Browse Source

Initial commit

master^2
witer33 2 years ago
parent
commit
e1e269295d
9 changed files with 461 additions and 0 deletions
  1. +8
    -0
      .idea/.gitignore
  2. +8
    -0
      .idea/modules.xml
  3. +9
    -0
      .idea/twitch.iml
  4. +3
    -0
      go.mod
  5. +39
    -0
      test.go
  6. +184
    -0
      twitchbot/bot.go
  7. +20
    -0
      twitchbot/builder.go
  8. +106
    -0
      twitchbot/client.go
  9. +84
    -0
      twitchbot/parser.go

+ 8
- 0
.idea/.gitignore View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

+ 8
- 0
.idea/modules.xml View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/twitchbot.iml" filepath="$PROJECT_DIR$/.idea/twitchbot.iml" />
</modules>
</component>
</project>

+ 9
- 0
.idea/twitch.iml View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

+ 3
- 0
go.mod View File

@ -0,0 +1,3 @@
module twitch
go 1.17

+ 39
- 0
test.go View File

@ -0,0 +1,39 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"twitch/twitchbot"
)
type Config struct {
Bot struct {
Token string `json:"token"`
Nick string `json:"nick"`
} `json:"bot"`
}
func main() {
var config Config
data, _ := ioutil.ReadFile("config.json")
err := json.Unmarshal(data, &config)
if err != nil {
log.Panicln(err)
}
bot := twitchbot.NewBot(config.Bot.Token, config.Bot.Nick, []string{"witer33"})
bot.OnMessage(func(bot *twitchbot.Bot, message *twitchbot.Message) {
fmt.Println(message)
if message.Message == "!ping" {
message.Reply("pong")
message.Delete()
}
})
bot.Run()
}

+ 184
- 0
twitchbot/bot.go View File

@ -0,0 +1,184 @@
package twitchbot
import "log"
type EventHandler struct {
messageHandlers []func(*Bot, *Message)
}
type Bot struct {
client *Client
host string
onLogin func(*Bot)
events EventHandler
channels []string
}
type User struct {
ID string
Name string
}
type Message struct {
ID string
Channel string
User *User
Message string
Bot *Bot
}
func ParseMessage(command *Command, bot *Bot) *Message {
return &Message{
ID: command.Tags["id"],
Channel: command.Args[0][1:],
User: &User{ID: command.Tags["user-id"], Name: command.Tags["display-name"]},
Message: command.Suffix,
Bot: bot,
}
}
func NewBot(token string, nick string, channels []string) *Bot {
client := Client{Token: token, Nick: nick}
return &Bot{client: &client, host: "irc.chat.twitch.tv:6667", events: EventHandler{}, channels: channels}
}
func (message *Message) Reply(msg string) error {
err := message.Bot.SendMessage(&Message{Message: msg, Channel: message.Channel})
if err != nil {
return err
}
return nil
}
func (message *Message) Delete() error {
err := message.Bot.DeleteMessage(&Message{ID: message.ID, Channel: message.Channel})
if err != nil {
return err
}
return nil
}
func (message *Message) Ban() error {
err := message.Bot.BanUser(message.Channel, message.User.Name)
if err != nil {
return err
}
return nil
}
func (event *EventHandler) configure(bot *Bot) {
bot.client.AddHandler("PRIVMSG", func(command *Command) bool {
message := ParseMessage(command, bot)
for _, handler := range event.messageHandlers {
handler(bot, message)
}
return true
})
}
func (bot *Bot) OnLogin(f func(*Bot)) {
bot.onLogin = f
}
func (bot *Bot) SendMessage(message *Message) error {
err := bot.client.Send(&Command{Command: "PRIVMSG", Args: []string{"#" + message.Channel}, Suffix: message.Message})
if err != nil {
return err
}
return nil
}
func (bot *Bot) DeleteMessage(message *Message) error {
err := bot.SendMessage(&Message{Channel: message.Channel, Message: "/delete " + message.ID})
if err != nil {
return err
}
return nil
}
func (bot *Bot) Join(channel string) error {
err := bot.client.Join("#" + channel)
if err != nil {
return err
}
return nil
}
func (bot *Bot) BanUser(channel string, user string) error {
err := bot.SendMessage(&Message{Channel: channel, Message: "/ban " + user})
if err != nil {
return err
}
return nil
}
func (bot *Bot) OnMessage(f func(*Bot, *Message)) {
bot.events.messageHandlers = append(bot.events.messageHandlers, f)
}
func (bot *Bot) GetClient() *Client {
return bot.client
}
func (bot *Bot) Run() {
for {
err := bot.Start()
if err != nil {
log.Printf("Bot error: %s\n", err)
}
}
}
func (bot *Bot) Start() error {
err := bot.client.Connect(bot.host)
if err != nil {
return err
}
defer bot.client.Close()
err = bot.client.Connect(bot.host)
if err != nil {
return err
}
err = bot.client.Auth()
if err != nil {
return err
}
bot.events.configure(bot)
bot.client.AddHandler("PING", func(command *Command) bool {
err := bot.client.Send(&Command{Command: "PONG", Suffix: "tmi.twitch.tv"})
if err != nil {
return false
}
return true
})
bot.client.AddHandler("376", func(command *Command) bool {
err = bot.GetClient().CapReq("twitch.tv/tags twitch.tv/commands")
if err != nil {
return false
}
for _, channel := range bot.channels {
err = bot.Join(channel)
if err != nil {
return false
}
}
if bot.onLogin != nil {
bot.onLogin(bot)
}
return true
})
err = bot.client.Handle()
if err != nil {
return err
}
return nil
}

+ 20
- 0
twitchbot/builder.go View File

@ -0,0 +1,20 @@
package twitchbot
import "strings"
func (c *Command) Build() string {
builder := strings.Builder{}
if c.Prefix != "" {
builder.WriteString(":" + c.Prefix + " ")
}
builder.WriteString(c.Command)
for _, arg := range c.Args {
if arg != "" {
builder.WriteString(" " + arg)
}
}
if c.Suffix != "" {
builder.WriteString(" :" + c.Suffix)
}
return builder.String()
}

+ 106
- 0
twitchbot/client.go View File

@ -0,0 +1,106 @@
package twitchbot
import (
"bufio"
"net"
"net/textproto"
)
type Client struct {
Token string
Nick string
conn net.Conn
writer *textproto.Writer
reader *textproto.Reader
handlers map[string][]func(*Command) bool
}
func (client *Client) Connect(host string) error {
conn, err := net.Dial("tcp", host)
if err != nil {
return err
}
client.conn = conn
client.writer = textproto.NewWriter(bufio.NewWriter(conn))
client.reader = textproto.NewReader(bufio.NewReader(conn))
return nil
}
func (client *Client) Auth() error {
err := client.writer.PrintfLine("PASS %s", client.Token)
if err != nil {
return err
}
err = client.writer.PrintfLine("NICK %s", client.Nick)
if err != nil {
return err
}
return nil
}
func (client *Client) AddHandler(command string, f func(*Command) bool) {
if client.handlers == nil {
client.handlers = make(map[string][]func(*Command) bool)
}
handlers, ok := client.handlers[command]
if !ok {
client.handlers[command] = []func(*Command) bool{f}
} else {
client.handlers[command] = append(handlers, f)
}
}
func (client *Client) Send(command *Command) error {
err := client.writer.PrintfLine(command.Build())
if err != nil {
return err
}
return nil
}
func (client *Client) CapReq(cap string) error {
err := client.Send(&Command{
Command: "CAP",
Args: []string{"REQ"},
Suffix: cap,
})
if err != nil {
return err
}
return nil
}
func (client *Client) Join(channel string) error {
err := client.Send(&Command{
Command: "JOIN",
Args: []string{channel},
})
if err != nil {
return err
}
return nil
}
func (client *Client) Close() {
err := client.conn.Close()
if err != nil {
return
}
}
func (client *Client) Handle() error {
for {
packet, err := client.reader.ReadLine()
if err != nil {
return err
}
command := ParsePacket(packet)
handlers := client.handlers[command.Command]
for _, handler := range handlers {
if !handler(command) {
return nil
}
}
}
}

+ 84
- 0
twitchbot/parser.go View File

@ -0,0 +1,84 @@
package twitchbot
import (
"strings"
)
type Command struct {
Tags map[string]string
Prefix string
Command string
Args []string
Suffix string
}
func (c *Command) String() string {
return "Prefix: " + c.Prefix + " Command: " + c.Command + " Args: " + strings.Join(c.Args, " ") + " Suffix: " + c.Suffix
}
func ReadString(reader *strings.Reader, until byte) string {
result := strings.Builder{}
char, ok := reader.ReadByte()
for ok == nil {
if char == until {
break
}
result.WriteByte(char)
char, ok = reader.ReadByte()
}
return result.String()
}
func ReadTags(reader *strings.Reader) map[string]string {
result := make(map[string]string)
for {
tag := ReadString(reader, '=')
if tag == "" {
break
}
value := ReadString(reader, ';')
result[tag] = value
}
return result
}
func ParsePacket(packet string) *Command {
reader := strings.NewReader(packet)
command := Command{}
args := make([]string, 15)
arg := 0
char, ok := reader.ReadByte()
for ok == nil {
if char == ':' && command.Prefix == "" && command.Command == "" {
command.Prefix = ReadString(reader, ' ')
} else if char == '@' && command.Tags == nil {
command.Tags = ReadTags(strings.NewReader(ReadString(reader, ' ')))
} else if command.Command == "" {
_, err := reader.Seek(-1, 1)
if err != nil {
continue
}
command.Command = ReadString(reader, ' ')
} else if char == ':' {
command.Suffix = ReadString(reader, '\r')
} else {
_, err := reader.Seek(-1, 1)
if err != nil {
continue
}
args[arg] = ReadString(reader, ' ')
arg++
}
char, ok = reader.ReadByte()
}
command.Args = args
return &command
}

Loading…
Cancel
Save