Skip to content

Commit 1a84d1e

Browse files
authored
Merge pull request #26 from kpetku/master
WIP: add onNewReconnectMessage and improve test coverage
2 parents a137208 + 5050be0 commit 1a84d1e

File tree

5 files changed

+165
-10
lines changed

5 files changed

+165
-10
lines changed

README.MD

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,11 @@ Channel is just a string like "lirik", note the absent #.
5454
### Message Types
5555

5656
If you ever need more than basic PRIVMSG this might be for you.
57-
These are the 5 major message types currently supported
57+
These are the 6 major message types currently supported
5858

5959
PRIVMSG
6060
WHISPER
6161
ROOMSTATE
6262
CLEARCHAT
6363
USERNOTICE
64+
RECONNECT

client.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type Client struct {
5050
onNewRoomstateMessage func(channel string, user User, message Message)
5151
onNewClearchatMessage func(channel string, user User, message Message)
5252
onNewUsernoticeMessage func(channel string, user User, message Message)
53+
onNewReconnectMessage func()
54+
quitKeepAlive chan struct{}
5355
}
5456

5557
// NewClient to create a new client
@@ -86,6 +88,12 @@ func (c *Client) OnNewUsernoticeMessage(callback func(channel string, user User,
8688
c.onNewUsernoticeMessage = callback
8789
}
8890

91+
// OnNewReconnectMessage attach callback to new reconnect message
92+
// or when a PING timeout causes keepConnectionAlive() to reconnect
93+
func (c *Client) OnNewReconnectMessage(callback func()) {
94+
c.onNewReconnectMessage = callback
95+
}
96+
8997
// Say write something in a chat
9098
func (c *Client) Say(channel, text string) {
9199
c.send(fmt.Sprintf("PRIVMSG #%s :%s", channel, text))
@@ -107,6 +115,9 @@ func (c *Client) Join(channel string) {
107115
// Disconnect close current connection
108116
func (c *Client) Disconnect() error {
109117
c.connActive.set(false)
118+
if c.quitKeepAlive != nil {
119+
close(c.quitKeepAlive)
120+
}
110121
if c.connection != nil {
111122
return c.connection.Close()
112123
}
@@ -172,9 +183,12 @@ func (c *Client) setupConnection() {
172183
go c.keepConnectionAlive()
173184
}
174185

186+
// keepAliveInterval is for decreasing the duration tests
187+
var keepAliveInterval = 500 * time.Second
188+
175189
func (c *Client) keepConnectionAlive() {
176-
ticker := time.NewTicker(500 * time.Second)
177-
quit := make(chan struct{})
190+
ticker := time.NewTicker(keepAliveInterval)
191+
c.quitKeepAlive = make(chan struct{})
178192
go func() {
179193
for {
180194
select {
@@ -184,7 +198,7 @@ func (c *Client) keepConnectionAlive() {
184198
c.Connect()
185199
}
186200
c.wasPinged.set(false)
187-
case <-quit:
201+
case <-c.quitKeepAlive:
188202
ticker.Stop()
189203
return
190204
}
@@ -204,6 +218,14 @@ func (c *Client) send(line string) {
204218
}
205219

206220
func (c *Client) handleLine(line string) {
221+
if line == ":tmi.twitch.tv RECONNECT" {
222+
if c.onNewReconnectMessage != nil {
223+
c.onNewReconnectMessage()
224+
}
225+
c.Disconnect()
226+
c.Connect()
227+
return
228+
}
207229
if strings.HasPrefix(line, "PING") {
208230
c.send(strings.Replace(line, "PING", "PONG", 1))
209231
c.wasPinged.set(true)

client_test.go

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"crypto/tls"
66
"fmt"
7+
"io"
78
"log"
89
"net/textproto"
910
"reflect"
@@ -50,7 +51,7 @@ func TestCanConnectAndAuthenticate(t *testing.T) {
5051

5152
for {
5253
message, err := tp.ReadLine()
53-
if err != nil {
54+
if err != nil && err != io.EOF {
5455
t.Fatal(err)
5556
}
5657
message = strings.Replace(message, "\r\n", "", 1)
@@ -82,6 +83,8 @@ func TestCanConnectAndAuthenticate(t *testing.T) {
8283
}
8384

8485
func TestCanDisconnect(t *testing.T) {
86+
keepAliveInterval = 9999 * time.Second
87+
8588
testMessage := "@badges=subscriber/6,premium/1;color=#FF0000;display-name=Redflamingo13;emotes=;id=2a31a9df-d6ff-4840-b211-a2547c7e656e;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1490382457309;turbo=0;user-id=78424343;user-type= :redflamingo13!redflamingo13@redflamingo13.tmi.twitch.tv PRIVMSG #pajlada :Thrashh5, FeelsWayTooAmazingMan kinda"
8689
wait := make(chan struct{})
8790

@@ -466,7 +469,7 @@ func TestCanSayMessage(t *testing.T) {
466469

467470
for {
468471
message, err := tp.ReadLine()
469-
if err != nil {
472+
if err != nil && err != io.EOF {
470473
t.Fatal(err)
471474
}
472475
message = strings.Replace(message, "\r\n", "", 1)
@@ -536,7 +539,7 @@ func TestCanWhisperMessage(t *testing.T) {
536539

537540
for {
538541
message, err := tp.ReadLine()
539-
if err != nil {
542+
if err != nil && err != io.EOF {
540543
t.Fatal(err)
541544
}
542545
message = strings.Replace(message, "\r\n", "", 1)
@@ -605,7 +608,7 @@ func TestCanJoinChannel(t *testing.T) {
605608

606609
for {
607610
message, err := tp.ReadLine()
608-
if err != nil {
611+
if err != nil && err != io.EOF {
609612
t.Fatal(err)
610613
}
611614
message = strings.Replace(message, "\r\n", "", 1)
@@ -674,7 +677,7 @@ func TestCanPong(t *testing.T) {
674677

675678
for {
676679
message, err := tp.ReadLine()
677-
if err != nil {
680+
if err != nil && err != io.EOF {
678681
t.Fatal(err)
679682
}
680683
message = strings.Replace(message, "\r\n", "", 1)
@@ -719,3 +722,117 @@ func TestCanNotDialInvalidAddress(t *testing.T) {
719722
t.Fatal("invalid Connect() error")
720723
}
721724
}
725+
726+
func TestCanCreateReconnectMessage(t *testing.T) {
727+
var reconnections int
728+
keepAliveInterval = 3 * time.Second
729+
testMessage := "@badges=subscriber/6,premium/1;color=#FF0000;display-name=Redflamingo13;emotes=;id=2a31a9df-d6ff-4840-b211-a2547c7e656e;mod=0;room-id=11148817;subscriber=1;tmi-sent-ts=1490382457309;turbo=0;user-id=78424343;user-type= :redflamingo13!redflamingo13@redflamingo13.tmi.twitch.tv PRIVMSG #pajlada :Thrashh5, FeelsWayTooAmazingMan kinda"
730+
wait := make(chan struct{})
731+
732+
go func() {
733+
cer, err := tls.LoadX509KeyPair("test_resources/server.crt", "test_resources/server.key")
734+
if err != nil {
735+
log.Println(err)
736+
return
737+
}
738+
config := &tls.Config{
739+
Certificates: []tls.Certificate{cer},
740+
}
741+
ln, err := tls.Listen("tcp", ":4331", config)
742+
if err != nil {
743+
t.Fatal(err)
744+
}
745+
close(wait)
746+
conn, err := ln.Accept()
747+
if err != nil {
748+
t.Fatal(err)
749+
}
750+
defer ln.Close()
751+
defer conn.Close()
752+
753+
fmt.Fprintf(conn, "%s\r\n", testMessage)
754+
}()
755+
756+
// wait for server to start
757+
select {
758+
case <-wait:
759+
case <-time.After(time.Second * 9):
760+
t.Fatal("server didn't start")
761+
}
762+
763+
client := NewClient("justinfan123123", "oauth:123123132")
764+
client.IrcAddress = ":4331"
765+
766+
client.OnNewReconnectMessage(func() {
767+
reconnections++
768+
})
769+
770+
go client.Connect()
771+
772+
waitMsg := make(chan string)
773+
774+
// wait for server to start
775+
select {
776+
case <-waitMsg:
777+
case <-time.After(time.Second * 6):
778+
if reconnections >= 1 {
779+
t.Fatal("failed to a recieve onReconnectEvent event")
780+
}
781+
}
782+
}
783+
784+
func TestCanReceiveReconnectMessage(t *testing.T) {
785+
var reconnections int
786+
testMessage := ":tmi.twitch.tv RECONNECT"
787+
wait := make(chan struct{})
788+
789+
go func() {
790+
cer, err := tls.LoadX509KeyPair("test_resources/server.crt", "test_resources/server.key")
791+
if err != nil {
792+
log.Println(err)
793+
return
794+
}
795+
config := &tls.Config{
796+
Certificates: []tls.Certificate{cer},
797+
}
798+
ln, err := tls.Listen("tcp", ":4332", config)
799+
if err != nil {
800+
t.Fatal(err)
801+
}
802+
close(wait)
803+
conn, err := ln.Accept()
804+
if err != nil {
805+
t.Fatal(err)
806+
}
807+
defer ln.Close()
808+
defer conn.Close()
809+
810+
fmt.Fprintf(conn, "%s\r\n", testMessage)
811+
}()
812+
813+
// wait for server to start
814+
select {
815+
case <-wait:
816+
case <-time.After(time.Second * 3):
817+
t.Fatal("server didn't start")
818+
}
819+
820+
client := NewClient("justinfan123123", "oauth:123123132")
821+
client.IrcAddress = ":4332"
822+
go client.Connect()
823+
824+
waitMsg := make(chan string)
825+
826+
client.OnNewReconnectMessage(func() {
827+
reconnections++
828+
close(waitMsg)
829+
})
830+
831+
// wait for server to start
832+
select {
833+
case <-waitMsg:
834+
case <-time.After(time.Second * 3):
835+
t.Fatal("no message sent")
836+
}
837+
assertIntsEqual(t, 1, reconnections)
838+
}

message.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const (
2020
ROOMSTATE = 3
2121
// USERNOTICE messages like subs, resubs, raids, etc
2222
USERNOTICE = 4
23+
// RECONNECT message
24+
RECONNECT = 5
2325
)
2426

2527
type message struct {
@@ -86,7 +88,10 @@ func parseMessage(line string) *message {
8688
func parseOtherMessage(line string) *message {
8789
msg := &message{}
8890
split := strings.Split(line, " ")
89-
91+
if split[1] == "RECONNECT" {
92+
msg.Type = RECONNECT
93+
return msg
94+
}
9095
switch split[2] {
9196
case "ROOMSTATE":
9297
msg.Type = ROOMSTATE

message_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,13 @@ func TestCanParseRoomstateMessage(t *testing.T) {
113113
t.Error("parsing ROOMSTATE message failed")
114114
}
115115
}
116+
func TestCanParseReconnectMessage(t *testing.T) {
117+
testMessage := `:tmi.twitch.tv RECONNECT`
118+
119+
message := parseOtherMessage(testMessage)
120+
121+
if message.Type != RECONNECT {
122+
t.Error("parsing RECONNECT message failed")
123+
}
124+
assertIntsEqual(t, 5, int(message.Type))
125+
}

0 commit comments

Comments
 (0)