Skip to content

Commit

Permalink
Merge pull request #182 from zneix/zneix/feat/handle-self-joins-parts
Browse files Browse the repository at this point in the history
Add callbacks for self JOIN/PART messages
  • Loading branch information
gempir authored Jun 12, 2022
2 parents fde60d2 + f773c9a commit 8cf13b4
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 30 deletions.
2 changes: 2 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ client.OnGlobalUserStateMessage(func(message GlobalUserStateMessage) {})
client.OnNoticeMessage(func(message NoticeMessage) {})
client.OnUserJoinMessage(func(message UserJoinMessage) {})
client.OnUserPartMessage(func(message UserPartMessage) {})
client.OnSelfJoinMessage(func(message UserJoinMessage) {})
client.OnSelfPartMessage(func(message UserPartMessage) {})
```

### Message Types
Expand Down
44 changes: 32 additions & 12 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ type Client struct {
onNoticeMessage func(message NoticeMessage)
onUserJoinMessage func(message UserJoinMessage)
onUserPartMessage func(message UserPartMessage)
onSelfJoinMessage func(message UserJoinMessage)
onSelfPartMessage func(message UserPartMessage)
onReconnectMessage func(message ReconnectMessage)
onNamesMessage func(message NamesMessage)
onPingMessage func(message PingMessage)
Expand Down Expand Up @@ -533,6 +535,18 @@ func (c *Client) OnUserPartMessage(callback func(message UserPartMessage)) {
c.onUserPartMessage = callback
}

// OnSelfJoinMessage attaches callback to user JOINs of client's own user
// Twitch will send us JOIN messages for our own user even without requesting twitch.tv/membership capability
func (c *Client) OnSelfJoinMessage(callback func(message UserJoinMessage)) {
c.onSelfJoinMessage = callback
}

// OnSelfJoinMessage attaches callback to user PARTs of client's own user
// Twitch will send us PART messages for our own user even without requesting twitch.tv/membership capability
func (c *Client) OnSelfPartMessage(callback func(message UserPartMessage)) {
c.onSelfPartMessage = callback
}

// OnReconnectMessage attaches callback that is triggered whenever the twitch servers tell us to reconnect
func (c *Client) OnReconnectMessage(callback func(message ReconnectMessage)) {
c.onReconnectMessage = callback
Expand Down Expand Up @@ -1037,15 +1051,25 @@ func (c *Client) handleLine(line string) error {
return c.handleNoticeMessage(*msg)

case *UserJoinMessage:
if c.handleUserJoinMessage(*msg) {
c.handleUserJoinMessage(*msg)
if msg.User == c.ircUser {
if c.onSelfJoinMessage != nil {
c.onSelfJoinMessage(*msg)
}
} else {
if c.onUserJoinMessage != nil {
c.onUserJoinMessage(*msg)
}
}
return nil

case *UserPartMessage:
if c.handleUserPartMessage(*msg) {
c.handleUserPartMessage(*msg)
if msg.User == c.ircUser {
if c.onSelfPartMessage != nil {
c.onSelfPartMessage(*msg)
}
} else {
if c.onUserPartMessage != nil {
c.onUserPartMessage(*msg)
}
Expand Down Expand Up @@ -1099,10 +1123,10 @@ func (c *Client) handleNoticeMessage(msg NoticeMessage) error {
return nil
}

func (c *Client) handleUserJoinMessage(msg UserJoinMessage) bool {
// Ignore own joins
func (c *Client) handleUserJoinMessage(msg UserJoinMessage) {
// Self JOINs are handled on a separate callback
if msg.User == c.ircUser {
return false
return
}

c.channelUserlistMutex.Lock()
Expand All @@ -1115,22 +1139,18 @@ func (c *Client) handleUserJoinMessage(msg UserJoinMessage) bool {
if _, ok := c.channelUserlist[msg.Channel][msg.User]; !ok {
c.channelUserlist[msg.Channel][msg.User] = true
}

return true
}

func (c *Client) handleUserPartMessage(msg UserPartMessage) bool {
// Ignore own parts
func (c *Client) handleUserPartMessage(msg UserPartMessage) {
// Self PARTs are handled on a separate callback
if msg.User == c.ircUser {
return false
return
}

c.channelUserlistMutex.Lock()
defer c.channelUserlistMutex.Unlock()

delete(c.channelUserlist[msg.Channel], msg.User)

return true
}

func (c *Client) handleNamesMessage(msg NamesMessage) {
Expand Down
75 changes: 57 additions & 18 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package twitch

import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
Expand Down Expand Up @@ -838,36 +839,55 @@ func TestCanReceiveJOINMessage(t *testing.T) {
assertMessageTypesEqual(t, JOIN, received.GetType())
}

func TestDoesNotReceiveJOINMessageFromSelf(t *testing.T) {
func TestReceiveJOINMessageWithSelfJOIN(t *testing.T) {
t.Parallel()
testMessages := []string{
`:[email protected] JOIN #mychannel`,
`:[email protected] JOIN #mychannel`,
}

wait := make(chan struct{})
var received UserJoinMessage
var receivedOther UserJoinMessage
var receivedSelf UserJoinMessage

host := startServer(t, postMessagesOnConnect(testMessages), nothingOnMessage)
client := newTestClient(host)

wg := new(sync.WaitGroup)
wg.Add(2)

client.OnUserJoinMessage(func(message UserJoinMessage) {
received = message
close(wait)
receivedOther = message
wg.Done()
})

client.OnSelfJoinMessage(func(message UserJoinMessage) {
receivedSelf = message
wg.Done()
})

go client.Connect()

// hack with ctx makes it possible to use it in select statement below
ctx, cancel := context.WithCancel(context.Background())
go func() {
wg.Wait()
cancel()
}()

// wait for server to start
select {
case <-wait:
case <-ctx.Done():
case <-time.After(time.Second * 3):
t.Fatal("no message sent")
}

assertStringsEqual(t, "username123", received.User)
assertStringsEqual(t, "mychannel", received.Channel)
assertMessageTypesEqual(t, JOIN, received.GetType())
assertStringsEqual(t, "username123", receivedOther.User)
assertStringsEqual(t, "mychannel", receivedOther.Channel)
assertMessageTypesEqual(t, JOIN, receivedOther.GetType())

assertStringsEqual(t, "justinfan123123", receivedSelf.User)
assertStringsEqual(t, "mychannel", receivedSelf.Channel)
assertMessageTypesEqual(t, JOIN, receivedSelf.GetType())
}

func TestCanReceivePARTMessage(t *testing.T) {
Expand Down Expand Up @@ -899,36 +919,55 @@ func TestCanReceivePARTMessage(t *testing.T) {
assertMessageTypesEqual(t, PART, received.GetType())
}

func TestDoesNotReceivePARTMessageFromSelf(t *testing.T) {
func TestReceivePARTMessageWithSelfPART(t *testing.T) {
t.Parallel()
testMessages := []string{
`:[email protected] PART #mychannel`,
`:[email protected] PART #mychannel`,
}

wait := make(chan struct{})
var received UserPartMessage
var receivedOther UserPartMessage
var receivedSelf UserPartMessage

host := startServer(t, postMessagesOnConnect(testMessages), nothingOnMessage)
client := newTestClient(host)

wg := new(sync.WaitGroup)
wg.Add(2)

client.OnUserPartMessage(func(message UserPartMessage) {
received = message
close(wait)
receivedOther = message
wg.Done()
})

client.OnSelfPartMessage(func(message UserPartMessage) {
receivedSelf = message
wg.Done()
})

go client.Connect()

// hack with ctx makes it possible to use it in select statement below
ctx, cancel := context.WithCancel(context.Background())
go func() {
wg.Wait()
cancel()
}()

// wait for server to start
select {
case <-wait:
case <-ctx.Done():
case <-time.After(time.Second * 3):
t.Fatal("no message sent")
}

assertStringsEqual(t, "username123", received.User)
assertStringsEqual(t, "mychannel", received.Channel)
assertMessageTypesEqual(t, PART, received.GetType())
assertStringsEqual(t, "username123", receivedOther.User)
assertStringsEqual(t, "mychannel", receivedOther.Channel)
assertMessageTypesEqual(t, PART, receivedOther.GetType())

assertStringsEqual(t, "justinfan123123", receivedSelf.User)
assertStringsEqual(t, "mychannel", receivedSelf.Channel)
assertMessageTypesEqual(t, PART, receivedSelf.GetType())
}

func TestCanReceiveUNSETMessage(t *testing.T) {
Expand Down

0 comments on commit 8cf13b4

Please sign in to comment.