· 4 years ago · May 06, 2021, 01:52 AM
1package main
2
3// A basic Twitch client that simply dumps out text from the Twitch IRC server
4// and prints it, and accepts commands from stdin and sends them.
5
6import (
7 "bufio"
8 "crypto/tls"
9 "fmt"
10 "net"
11 "os"
12 "os/signal"
13 "strings"
14)
15
16const twitchURL = "irc.chat.twitch.tv:6697"
17
18func main() {
19 nick := os.Getenv("BOT_USERNAME")
20 oauth := os.Getenv("OAUTH_TOKEN")
21
22 if nick == "" || oauth == "" {
23 fmt.Println("Specify both BOT_USERNAME and OAUTH_TOKEN environment variables.")
24 return
25 }
26
27 conn, err := tls.Dial("tcp", twitchURL, nil)
28 if err != nil {
29 fmt.Printf("TLS connection to %q did not succeed: %v\n", twitchURL, err)
30 return
31 }
32 defer conn.Close()
33
34 fmt.Println("PASS <elided>")
35 fmt.Fprintf(conn, "PASS %s\r\n", oauth) // Don't call send to avoid echoing the token.
36 send(conn, "NICK "+nick)
37
38 send(conn, "CAP REQ :twitch.tv/tags") // Enable IRCv3 message tags.
39 send(conn, "CAP REQ :twitch.tv/membership") // Enable JOIN/PART messages.
40 send(conn, "CAP REQ :twitch.tv/commands") // Enable Twitch commands.
41
42 go stdin(conn)
43 go stdout(conn)
44
45 signals := make(chan os.Signal, 1)
46 signal.Notify(signals, os.Interrupt)
47
48 for {
49 select {
50 case sig := <-signals:
51 fmt.Printf("Signal %q received.\n", sig)
52 return
53 }
54 }
55}
56
57func stdin(conn net.Conn) {
58 scanner := bufio.NewScanner(os.Stdin)
59 for scanner.Scan() {
60 txt := scanner.Text() // No final '\n'.
61 if len(txt) == 0 {
62 continue
63 }
64 if txt[1] == '/' {
65 if err := sendCommand(conn, txt); err != nil {
66 fmt.Printf("Command %q error: %v\n", txt, err)
67 }
68 continue
69 }
70 fmt.Fprintf(conn, "%s\r\n", txt)
71 if err := scanner.Err(); err != nil {
72 fmt.Println("Error from stdin scanner:", err)
73 continue
74 }
75 }
76}
77
78// Print incoming messages from the server to stdout.
79func stdout(conn net.Conn) {
80 scanner := bufio.NewScanner(conn)
81 for scanner.Scan() {
82 msg := scanner.Text()
83 if len(msg) == 0 {
84 continue
85 }
86
87 fmt.Println(msg)
88
89 if strings.HasPrefix(msg, "PING ") {
90 pong(conn, msg)
91 continue
92 }
93
94 //printNonPrivMsgs(msg)
95 if err := scanner.Err(); err != nil {
96 fmt.Println("Error from stdout scanner:", err)
97 continue
98 }
99 }
100}
101
102func pong(conn net.Conn, msg string) {
103 parts := strings.Fields(msg)
104
105 s := fmt.Sprintf("PONG %s", parts[1])
106 fmt.Println(s)
107
108 fmt.Fprintf(conn, "%s\r\n", s)
109}
110
111func printNonPrivMsgs(msg string) {
112 parts := strings.SplitN(msg, " :", 3)
113 if len(parts) < 3 {
114 fmt.Printf("(%d) %#v\n", len(parts), parts)
115 } else {
116 if !strings.Contains(parts[1], " PRIVMSG ") {
117 fmt.Printf("(%d) %#v\n", len(parts), parts)
118 }
119 }
120
121}
122
123func send(conn net.Conn, txt string) {
124 fmt.Println(txt)
125 fmt.Fprintf(conn, "%s\r\n", txt)
126}
127
128func sendCommand(conn net.Conn, command string) error {
129 parts := strings.Fields(command[1:])
130 if len(parts) < 1 {
131 return fmt.Errorf("invalid format for %q\n", command)
132 }
133
134 fmt.Println("Would send command:", command)
135
136 return nil
137}