mirror of
https://github.com/gissleh/irc.git
synced 2026-09-29 00:05:34 +00:00
clean up code, add client-specific handlers.
This commit is contained in:
@@ -43,15 +43,15 @@ var ErrNoConnection = errors.New("irc: no connection")
|
||||
// added to the client.
|
||||
var ErrTargetAlreadyAdded = errors.New("irc: target already added")
|
||||
|
||||
// ErrTargetConflict is returned by Clinet.AddTarget if there already exists a target
|
||||
// ErrTargetConflict is returned by Client.AddTarget if there already exists a target
|
||||
// matching the name and kind.
|
||||
var ErrTargetConflict = errors.New("irc: target name and kind match existing target")
|
||||
|
||||
// ErrTargetNotFound is returned by Clinet.RemoveTarget if the target is not part of
|
||||
// ErrTargetNotFound is returned by Client.RemoveTarget if the target is not part of
|
||||
// the client's target list
|
||||
var ErrTargetNotFound = errors.New("irc: target not found")
|
||||
|
||||
// ErrTargetIsStatus is returned by Clinet.RemoveTarget if the target is the client's
|
||||
// ErrTargetIsStatus is returned by Client.RemoveTarget if the target is the client's
|
||||
// status target
|
||||
var ErrTargetIsStatus = errors.New("irc: cannot remove status target")
|
||||
|
||||
@@ -85,6 +85,8 @@ type Client struct {
|
||||
status *Status
|
||||
targets []Target
|
||||
targetIds map[Target]string
|
||||
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
// New creates a new client. The context can be context.Background if you want manually to
|
||||
@@ -102,7 +104,7 @@ func New(ctx context.Context, config Config) *Client {
|
||||
status: &Status{},
|
||||
}
|
||||
|
||||
client.AddTarget(client.status)
|
||||
_, _ = client.AddTarget(client.status)
|
||||
|
||||
client.ctx, client.cancel = context.WithCancel(ctx)
|
||||
|
||||
@@ -174,6 +176,7 @@ func (client *Client) Ready() bool {
|
||||
|
||||
func (client *Client) State() ClientState {
|
||||
client.mutex.RLock()
|
||||
|
||||
state := ClientState{
|
||||
Nick: client.nick,
|
||||
User: client.user,
|
||||
@@ -190,6 +193,7 @@ func (client *Client) State() ClientState {
|
||||
state.Caps = append(state.Caps, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(state.Caps)
|
||||
|
||||
for _, target := range client.targets {
|
||||
tstate := target.State()
|
||||
@@ -200,8 +204,6 @@ func (client *Client) State() ClientState {
|
||||
|
||||
client.mutex.RUnlock()
|
||||
|
||||
sort.Strings(state.Caps)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -210,7 +212,7 @@ func (client *Client) Connect(addr string, ssl bool) (err error) {
|
||||
var conn net.Conn
|
||||
|
||||
if client.Connected() {
|
||||
client.Disconnect()
|
||||
_ = client.Disconnect()
|
||||
}
|
||||
|
||||
client.isupport.Reset()
|
||||
@@ -219,7 +221,7 @@ func (client *Client) Connect(addr string, ssl bool) (err error) {
|
||||
client.quit = false
|
||||
client.mutex.Unlock()
|
||||
|
||||
client.EmitSync(context.Background(), NewEvent("client", "connecting"))
|
||||
_ = client.EmitSync(context.Background(), NewEvent("client", "connecting"))
|
||||
|
||||
if ssl {
|
||||
conn, err = tls.Dial("tcp", addr, &tls.Config{
|
||||
@@ -283,9 +285,7 @@ func (client *Client) Disconnect() error {
|
||||
|
||||
client.quit = true
|
||||
|
||||
err := client.conn.Close()
|
||||
|
||||
return err
|
||||
return client.conn.Close()
|
||||
}
|
||||
|
||||
// Connected returns true if the client has a connection
|
||||
@@ -314,7 +314,7 @@ func (client *Client) Send(line string) error {
|
||||
_, err := conn.Write([]byte(line))
|
||||
if err != nil {
|
||||
client.EmitNonBlocking(NewErrorEvent("network", err.Error()))
|
||||
client.Disconnect()
|
||||
_ = client.Disconnect()
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -402,7 +402,7 @@ func (client *Client) Emit(event Event) context.Context {
|
||||
return event.ctx
|
||||
}
|
||||
|
||||
// EmitNonBlocking is just like emit, but it will spin off a goroutine if the channel is full.
|
||||
// EmitNonBlocking is just like emitInGlobalHandlers, but it will spin off a goroutine if the channel is full.
|
||||
// This lets it be called from other handlers without ever blocking. See Emit for what the
|
||||
// returned context is for.
|
||||
func (client *Client) EmitNonBlocking(event Event) context.Context {
|
||||
@@ -476,7 +476,7 @@ func (client *Client) SetValue(key string, value interface{}) {
|
||||
// Destroy destroys the client, which will lead to a disconnect. Cancelling the
|
||||
// parent context will do the same.
|
||||
func (client *Client) Destroy() {
|
||||
client.Disconnect()
|
||||
_ = client.Disconnect()
|
||||
client.cancel()
|
||||
close(client.sends)
|
||||
close(client.events)
|
||||
@@ -668,6 +668,14 @@ func (client *Client) FindUser(nick string) (u list.User, ok bool) {
|
||||
return list.User{}, false
|
||||
}
|
||||
|
||||
// AddHandler adds a handler. This is thread safe, unlike adding global handlers.
|
||||
func (client *Client) AddHandler(handler Handler) {
|
||||
client.mutex.Lock()
|
||||
client.handlers = append(client.handlers[:0], client.handlers...)
|
||||
client.handlers = append(client.handlers, handler)
|
||||
client.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (client *Client) handleEventLoop() {
|
||||
ticker := time.NewTicker(time.Second * 30)
|
||||
|
||||
@@ -680,7 +688,6 @@ func (client *Client) handleEventLoop() {
|
||||
}
|
||||
|
||||
client.handleEvent(event)
|
||||
emit(event, client)
|
||||
|
||||
// Turn an unhandled input into a raw command.
|
||||
if event.kind == "input" && !event.preventedDefault {
|
||||
@@ -695,7 +702,6 @@ func (client *Client) handleEventLoop() {
|
||||
event.ctx, event.cancel = context.WithCancel(client.ctx)
|
||||
|
||||
client.handleEvent(&event)
|
||||
emit(&event, client)
|
||||
|
||||
event.cancel()
|
||||
}
|
||||
@@ -710,13 +716,12 @@ end:
|
||||
|
||||
ticker.Stop()
|
||||
|
||||
client.Disconnect()
|
||||
_ = client.Disconnect()
|
||||
|
||||
event := NewEvent("client", "destroy")
|
||||
event.ctx, event.cancel = context.WithCancel(client.ctx)
|
||||
|
||||
client.handleEvent(&event)
|
||||
emit(&event, client)
|
||||
|
||||
event.cancel()
|
||||
}
|
||||
@@ -742,7 +747,7 @@ func (client *Client) handleSendLoop() {
|
||||
queue = client.config.SendRate - 1
|
||||
}
|
||||
|
||||
client.Send(line)
|
||||
_ = client.Send(line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,7 +776,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.mutex.RUnlock()
|
||||
|
||||
if lastSend > time.Second*120 {
|
||||
client.Sendf("PING :%x%x%x", mathRand.Int63(), mathRand.Int63(), mathRand.Int63())
|
||||
_ = client.Sendf("PING :%x%x%x", mathRand.Int63(), mathRand.Int63(), mathRand.Int63())
|
||||
}
|
||||
}
|
||||
case "packet.ping":
|
||||
@@ -784,7 +789,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
message += " :" + event.Text
|
||||
}
|
||||
|
||||
client.Send(message)
|
||||
_ = client.Send(message)
|
||||
}
|
||||
|
||||
// Client Registration
|
||||
@@ -796,11 +801,11 @@ func (client *Client) handleEvent(event *Event) {
|
||||
delete(client.capEnabled, key)
|
||||
}
|
||||
client.mutex.Unlock()
|
||||
client.Send("CAP LS 302")
|
||||
_ = client.Send("CAP LS 302")
|
||||
|
||||
// Send server password if configured.
|
||||
if client.config.Password != "" {
|
||||
client.Sendf("PASS :%s", client.config.Password)
|
||||
_ = client.Sendf("PASS :%s", client.config.Password)
|
||||
}
|
||||
|
||||
// Reuse nick or get from config
|
||||
@@ -812,19 +817,22 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.mutex.RUnlock()
|
||||
|
||||
// Start registration.
|
||||
client.Sendf("NICK %s", nick)
|
||||
client.Sendf("USER %s 8 * :%s", client.config.User, client.config.RealName)
|
||||
_ = client.Sendf("NICK %s", nick)
|
||||
_ = client.Sendf("USER %s 8 * :%s", client.config.User, client.config.RealName)
|
||||
}
|
||||
|
||||
// Welcome message
|
||||
case "packet.001":
|
||||
{
|
||||
client.mutex.Lock()
|
||||
client.nick = event.Args[0]
|
||||
client.mutex.Unlock()
|
||||
|
||||
client.Sendf("WHO %s", event.Args[0])
|
||||
// Send a WHO right away to gather enough client information for precise message cutting.
|
||||
_ = client.Sendf("WHO %s", event.Args[0])
|
||||
}
|
||||
|
||||
// Nick rotation
|
||||
case "packet.431", "packet.432", "packet.433", "packet.436":
|
||||
{
|
||||
client.mutex.RLock()
|
||||
@@ -839,7 +847,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
sent := false
|
||||
for _, alt := range client.config.Alternatives {
|
||||
if nick == prev {
|
||||
client.Sendf("NICK %s", alt)
|
||||
_ = client.Sendf("NICK %s", alt)
|
||||
sent = true
|
||||
break
|
||||
}
|
||||
@@ -849,7 +857,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
|
||||
if !sent {
|
||||
// "LastAlt" -> "Nick23962"
|
||||
client.Sendf("NICK %s%05d", client.config.Nick, mathRand.Int31n(99999))
|
||||
_ = client.Sendf("NICK %s%05d", client.config.Nick, mathRand.Int31n(99999))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -918,9 +926,9 @@ func (client *Client) handleEvent(event *Event) {
|
||||
requestedCaps := strings.Join(client.capsRequested, " ")
|
||||
client.mutex.RUnlock()
|
||||
|
||||
client.Send("CAP REQ :" + requestedCaps)
|
||||
_ = client.Send("CAP REQ :" + requestedCaps)
|
||||
} else {
|
||||
client.Send("CAP END")
|
||||
_ = client.Send("CAP END")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,7 +942,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.mutex.Unlock()
|
||||
}
|
||||
|
||||
client.Send("CAP END")
|
||||
_ = client.Send("CAP END")
|
||||
}
|
||||
case "NAK":
|
||||
{
|
||||
@@ -954,7 +962,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
requestedCaps := strings.Join(client.capsRequested, " ")
|
||||
client.mutex.RUnlock()
|
||||
|
||||
client.Send("CAP REQ :" + requestedCaps)
|
||||
_ = client.Send("CAP REQ :" + requestedCaps)
|
||||
}
|
||||
case "NEW":
|
||||
{
|
||||
@@ -969,7 +977,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
}
|
||||
|
||||
if len(requests) > 0 {
|
||||
client.Send("CAP REQ :" + strings.Join(requests, " "))
|
||||
_ = client.Send("CAP REQ :" + strings.Join(requests, " "))
|
||||
}
|
||||
}
|
||||
case "DEL":
|
||||
@@ -1021,7 +1029,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
|
||||
if event.Nick == client.nick {
|
||||
channel = &Channel{name: event.Arg(0), userlist: list.New(&client.isupport)}
|
||||
client.AddTarget(channel)
|
||||
_, _ = client.AddTarget(channel)
|
||||
} else {
|
||||
channel = client.Channel(event.Arg(0))
|
||||
}
|
||||
@@ -1038,7 +1046,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
|
||||
if event.Nick == client.nick {
|
||||
channel.parted = true
|
||||
client.RemoveTarget(channel)
|
||||
_, _ = client.RemoveTarget(channel)
|
||||
} else {
|
||||
client.handleInTarget(channel, event)
|
||||
}
|
||||
@@ -1053,7 +1061,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
|
||||
if event.Arg(1) == client.nick {
|
||||
channel.parted = true
|
||||
client.RemoveTarget(channel)
|
||||
_, _ = client.RemoveTarget(channel)
|
||||
} else {
|
||||
client.handleInTarget(channel, event)
|
||||
}
|
||||
@@ -1095,9 +1103,8 @@ func (client *Client) handleEvent(event *Event) {
|
||||
// Message parsing
|
||||
case "packet.privmsg", "ctcp.action":
|
||||
{
|
||||
// Target the mssage
|
||||
// Target the message
|
||||
target := Target(client.status)
|
||||
spawned := false
|
||||
targetName := event.Arg(0)
|
||||
if targetName == client.nick {
|
||||
target := client.Target("query", targetName)
|
||||
@@ -1108,9 +1115,8 @@ func (client *Client) handleEvent(event *Event) {
|
||||
Host: event.Host,
|
||||
}}
|
||||
|
||||
client.AddTarget(query)
|
||||
|
||||
spawned = true
|
||||
id, _ := client.AddTarget(query)
|
||||
event.RenderTags["spawned"] = id
|
||||
|
||||
target = query
|
||||
}
|
||||
@@ -1128,10 +1134,6 @@ func (client *Client) handleEvent(event *Event) {
|
||||
}
|
||||
|
||||
client.handleInTarget(target, event)
|
||||
|
||||
if spawned {
|
||||
// TODO: Message has higher importance // 0:Normal, 1:Important, 2:Highlight
|
||||
}
|
||||
}
|
||||
|
||||
case "packet.notice":
|
||||
@@ -1202,7 +1204,7 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.mutex.RUnlock()
|
||||
|
||||
if len(channels) > 0 {
|
||||
client.Sendf("JOIN %s", strings.Join(channels, ","))
|
||||
_ = client.Sendf("JOIN %s", strings.Join(channels, ","))
|
||||
client.EmitNonBlocking(rejoinEvent)
|
||||
}
|
||||
|
||||
@@ -1217,6 +1219,17 @@ func (client *Client) handleEvent(event *Event) {
|
||||
if len(event.targets) == 0 {
|
||||
client.handleInTarget(client.status, event)
|
||||
}
|
||||
|
||||
client.mutex.RLock()
|
||||
clientHandlers := client.handlers
|
||||
client.mutex.RUnlock()
|
||||
|
||||
for _, handler := range globalHandlers {
|
||||
handler(event, client)
|
||||
}
|
||||
for _, handler := range clientHandlers {
|
||||
handler(event, client)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) handleInTargets(nick string, event *Event) {
|
||||
|
||||
+4
-4
@@ -35,7 +35,7 @@ func TestClient(t *testing.T) {
|
||||
{Client: "NICK Test"},
|
||||
{Client: "USER Tester 8 * :..."},
|
||||
{Server: ":testserver.example.com CAP * LS :multi-prefix chghost userhost-in-names vendorname/custom-stuff echo-message =malformed vendorname/advanced-custom-stuff=things,and,items"},
|
||||
{Client: "CAP REQ :multi-prefix chghost userhost-in-names"},
|
||||
{Client: "CAP REQ :multi-prefix chghost userhost-in-names echo-message"},
|
||||
{Server: ":testserver.example.com CAP * ACK :multi-prefix userhost-in-names"},
|
||||
{Client: "CAP END"},
|
||||
{Callback: func() error {
|
||||
@@ -377,8 +377,8 @@ func TestParenthesesBug(t *testing.T) {
|
||||
Nick: "Stuff",
|
||||
})
|
||||
|
||||
irc.AddHandler(func(event *irc.Event, client *irc.Client) {
|
||||
if event.Name() == "packet.privmsg" || event.Nick == "Dante" {
|
||||
client.AddHandler(func(event *irc.Event, client *irc.Client) {
|
||||
if event.Name() == "packet.privmsg" || event.Nick == "Beans" {
|
||||
gotMessage = true
|
||||
|
||||
if event.Text != "((Remove :01 goofs!*))" {
|
||||
@@ -388,7 +388,7 @@ func TestParenthesesBug(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
packet, err := irc.ParsePacket("@example/tag=32; :Dante!TheBeans@captain.purple.beans PRIVMSG Stuff :((Remove :01 goofs!*))")
|
||||
packet, err := irc.ParsePacket("@example/tag=32; :Beans!beans@beans.example.com PRIVMSG Stuff :((Remove :01 goofs!*))")
|
||||
if err != nil {
|
||||
t.Error("Parse", err)
|
||||
}
|
||||
|
||||
@@ -4,25 +4,17 @@ package irc
|
||||
// events.
|
||||
type Handler func(event *Event, client *Client)
|
||||
|
||||
var eventHandler struct {
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
func emit(event *Event, client *Client) {
|
||||
for _, handler := range eventHandler.handlers {
|
||||
handler(event, client)
|
||||
}
|
||||
}
|
||||
var globalHandlers = make([]Handler, 0, 8)
|
||||
|
||||
// AddHandler adds a new handler to the irc handling. The handler may be called from multiple threads at the same
|
||||
// time, so external resources should be locked if there are multiple clients. Adding handlers is not thread
|
||||
// safe and should be done prior to clients being created.AddHandler. Also, this handler will block the individual
|
||||
// safe and should be done prior to clients being created. Also, this handler will block the individual
|
||||
// client's event loop, so long operations that include network requests and the like should be done in a
|
||||
// goroutine with the needed data **copied** from the handler function.
|
||||
func AddHandler(handler Handler) {
|
||||
eventHandler.handlers = append(eventHandler.handlers, handler)
|
||||
globalHandlers = append(globalHandlers, handler)
|
||||
}
|
||||
|
||||
func init() {
|
||||
eventHandler.handlers = make([]Handler, 0, 8)
|
||||
globalHandlers = make([]Handler, 0, 8)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func (interaction *Interaction) Listen() (addr string, err error) {
|
||||
line := lines[i]
|
||||
|
||||
if line.Server != "" {
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(time.Second * 2))
|
||||
_, err := conn.Write(append([]byte(line.Server), '\r', '\n'))
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
@@ -58,7 +59,7 @@ func (interaction *Interaction) Listen() (addr string, err error) {
|
||||
return
|
||||
}
|
||||
} else if line.Client != "" {
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * 2))
|
||||
_ = conn.SetReadDeadline(time.Now().Add(time.Second * 2))
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
|
||||
@@ -49,12 +49,12 @@ func (isupport *ISupport) ParsePrefixedNick(fullnick string) (nick, modes, prefi
|
||||
isupport.lock.RLock()
|
||||
defer isupport.lock.RUnlock()
|
||||
|
||||
if fullnick == "" || isupport.state.Prefixes == nil {
|
||||
if fullnick == "" || isupport.state.PrefixMap == nil {
|
||||
return fullnick, "", ""
|
||||
}
|
||||
|
||||
for i, ch := range fullnick {
|
||||
if mode, ok := isupport.state.Prefixes[ch]; ok {
|
||||
if mode, ok := isupport.state.PrefixMap[ch]; ok {
|
||||
modes += string(mode)
|
||||
prefixes += string(ch)
|
||||
} else {
|
||||
@@ -163,7 +163,7 @@ func (isupport *ISupport) Mode(prefix rune) rune {
|
||||
isupport.lock.RLock()
|
||||
defer isupport.lock.RUnlock()
|
||||
|
||||
return isupport.state.Prefixes[prefix]
|
||||
return isupport.state.PrefixMap[prefix]
|
||||
}
|
||||
|
||||
// Prefix gets the prefix for the mode. It's a bit slower
|
||||
@@ -173,7 +173,7 @@ func (isupport *ISupport) Prefix(mode rune) rune {
|
||||
isupport.lock.RLock()
|
||||
defer isupport.lock.RUnlock()
|
||||
|
||||
for prefix, mappedMode := range isupport.state.Prefixes {
|
||||
for prefix, mappedMode := range isupport.state.PrefixMap {
|
||||
if mappedMode == mode {
|
||||
return prefix
|
||||
}
|
||||
@@ -182,7 +182,7 @@ func (isupport *ISupport) Prefix(mode rune) rune {
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
// Prefixes gets the prefixes in the order of the modes, skipping any
|
||||
// PrefixMap gets the prefixes in the order of the modes, skipping any
|
||||
// invalid modes.
|
||||
func (isupport *ISupport) Prefixes(modes string) string {
|
||||
result := ""
|
||||
@@ -280,9 +280,9 @@ func (isupport *ISupport) Set(key, value string) {
|
||||
|
||||
isupport.state.PrefixOrder = split[1]
|
||||
isupport.state.ModeOrder = split[0]
|
||||
isupport.state.Prefixes = make(map[rune]rune, len(split[0]))
|
||||
isupport.state.PrefixMap = make(map[rune]rune, len(split[0]))
|
||||
for i, ch := range split[0] {
|
||||
isupport.state.Prefixes[rune(split[1][i])] = ch
|
||||
isupport.state.PrefixMap[rune(split[1][i])] = ch
|
||||
}
|
||||
}
|
||||
case "CHANMODES": // CHANMODES=eIbq,k,flj,CFLNPQcgimnprstz
|
||||
@@ -304,7 +304,7 @@ func (isupport *ISupport) Reset() {
|
||||
isupport.lock.Lock()
|
||||
isupport.state.PrefixOrder = ""
|
||||
isupport.state.ModeOrder = ""
|
||||
isupport.state.Prefixes = nil
|
||||
isupport.state.PrefixMap = nil
|
||||
isupport.state.ChannelModes = nil
|
||||
|
||||
for key := range isupport.state.Raw {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package isupport
|
||||
|
||||
type State struct {
|
||||
Raw map[string]string `json:"raw"`
|
||||
Prefixes map[rune]rune `json:"-"`
|
||||
PrefixMap map[rune]rune `json:"prefixMap"`
|
||||
ModeOrder string `json:"modeOrder"`
|
||||
PrefixOrder string `json:"prefixOrder"`
|
||||
ChannelModes []string `json:"channelModes"`
|
||||
|
||||
Reference in New Issue
Block a user