You can now run simple yes/no polls.

master
Zed A. Shaw 4 weeks ago
parent 8512090ae6
commit aa1a6f826f
  1. 1
      .gitignore
  2. 221
      main.go

1
.gitignore vendored

@ -34,3 +34,4 @@ public
*.log *.log
secret.json secret.json
config.json config.json
actions.json

@ -1,19 +1,23 @@
package main package main
import ( import (
"encoding/json"
"errors"
"flag"
"fmt"
"log" "log"
"net/http"
"net/url"
"os" "os"
"regexp"
"strings" "strings"
"encoding/json"
"time" "time"
"net/http"
"net/url"
"fmt"
"flag"
"github.com/gempir/go-twitch-irc/v4" "github.com/gempir/go-twitch-irc/v4"
) )
var COMMAND_RE = regexp.MustCompile(`^(\$|!)([a-z_]+) ?(.*)?$`)
type Secret struct { type Secret struct {
ClientID string ClientID string
ClientSecret string ClientSecret string
@ -34,6 +38,26 @@ type Config struct {
Periodic []Periodic Periodic []Periodic
} }
type Poll struct {
Active bool
Text string
Yes int
No int
}
type Bot struct {
Secrets Secret
Config Config
Client *twitch.Client
CurrentPoll Poll
}
type Command struct {
Type string
Command string
Text string
}
type TwitchTokenResponse struct { type TwitchTokenResponse struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
@ -60,13 +84,13 @@ func RunPeriodic(message Periodic, channel string, client *twitch.Client) {
} }
} }
func RefreshToken(secrets* Secret) { func (bot *Bot) RefreshToken() {
api_url := "https://id.twitch.tv/oauth2/token" api_url := "https://id.twitch.tv/oauth2/token"
data := url.Values{} data := url.Values{}
data.Set("client_id", secrets.ClientID) data.Set("client_id", bot.Secrets.ClientID)
data.Set("client_secret", secrets.ClientSecret) data.Set("client_secret", bot.Secrets.ClientSecret)
data.Set("grant_type", "refresh_token") data.Set("grant_type", "refresh_token")
data.Set("refresh_token", secrets.RefreshToken) data.Set("refresh_token", bot.Secrets.RefreshToken)
encoded := strings.NewReader(data.Encode()) encoded := strings.NewReader(data.Encode())
resp, err := http.Post(api_url,"application/x-www-form-urlencoded", encoded) resp, err := http.Post(api_url,"application/x-www-form-urlencoded", encoded)
@ -83,10 +107,10 @@ func RefreshToken(secrets* Secret) {
err = json.NewDecoder(resp.Body).Decode(&token_resp) err = json.NewDecoder(resp.Body).Decode(&token_resp)
if err != nil { log.Fatal(err) } if err != nil { log.Fatal(err) }
secrets.AccessToken = fmt.Sprintf("oauth:%s", token_resp.AccessToken); bot.Secrets.AccessToken = fmt.Sprintf("oauth:%s", token_resp.AccessToken);
} }
func LoadConfigs() (Secret, Config) { func (bot *Bot) LoadConfigs() {
var secrets_file string var secrets_file string
var config_file string var config_file string
var action_file string var action_file string
@ -100,71 +124,172 @@ func LoadConfigs() (Secret, Config) {
log.Println("Loading config from", config_file) log.Println("Loading config from", config_file)
log.Println("Loading actions from", action_file) log.Println("Loading actions from", action_file)
secrets := LoadJSON[Secret](secrets_file) bot.Secrets = LoadJSON[Secret](secrets_file)
config := LoadJSON[Config](config_file) bot.Config = LoadJSON[Config](config_file)
config.Actions = LoadJSON[map[string]string](action_file) bot.Config.Actions = LoadJSON[map[string]string](action_file)
}
func ParseCommand(message string) (Command, error) {
matches := COMMAND_RE.FindStringSubmatch(message)
if matches == nil {
return Command{}, errors.New("Invalid command format.")
}
result := Command{
matches[1],
matches[2],
matches[3],
}
return secrets, config return result, nil
} }
func main() { func (bot *Bot) StreamerAction(message twitch.PrivateMessage) {
SECRETS, CONFIG := LoadConfigs() if !message.User.IsBroadcaster {
log.Println("! A StreamerAction message was sent by someone not marked as Broadcaster")
return
}
RefreshToken(&SECRETS) cmd, err := ParseCommand(message.Message)
if err != nil {
fmt.Println(err)
return
}
// or client := twitch.NewAnonymousClient() for an anonymous user (no write capabilities) switch cmd.Command {
client := twitch.NewClient(CONFIG.ClientNick, SECRETS.AccessToken) case "poll_start":
if cmd.Text == "" { return }
client.OnPrivateMessage(func(message twitch.PrivateMessage) { bot.CurrentPoll.Active = true
// not sure if this is good enough for auth bot.CurrentPoll.Text = cmd.Text
bot.CurrentPoll.Yes = 0
bot.CurrentPoll.No = 0
cmd, found := strings.CutPrefix(message.Message, "!") announce := fmt.Sprintf("New Poll: %s. Use 1, yes or true; 0, no or false to reply.", bot.CurrentPoll.Text)
if !found { return }
// see if it's a valid action bot.Send(message, announce)
action, is_action := CONFIG.Actions[strings.ToLower(cmd)] case "poll_end":
bot.CurrentPoll.Active = false
bot.CurrentPoll.Text = fmt.Sprintf(
"Poll Has Ended: \"%s\" Yes=%d, No=%d",
bot.CurrentPoll.Text, bot.CurrentPoll.Yes, bot.CurrentPoll.No)
bot.Send(message, bot.CurrentPoll.Text)
}
}
if is_action && message.User.IsBroadcaster {
log.Println("Hitting URL", action)
return;
}
func (bot *Bot) TryPoll(message twitch.PrivateMessage) bool {
switch message.Message {
case "yes", "1", "true":
bot.CurrentPoll.Yes += 1
return true
case "no", "0", "false":
bot.CurrentPoll.No += 1
return true
}
return false
}
func (bot *Bot) Send(message twitch.PrivateMessage, text string) {
bot.Client.Say(message.Channel, text)
}
func (bot *Bot) Reply(message twitch.PrivateMessage, text string) {
bot.Client.Reply(message.Channel, message.ID, text)
}
func (bot *Bot) SendHelp(message twitch.PrivateMessage) {
bot.Reply(message, "Invalid command. Use !help to see what's available.")
}
func (bot *Bot) ViewerAction(message twitch.PrivateMessage) {
cmd, err := ParseCommand(message.Message)
if err != nil || cmd.Type != "!" {
bot.SendHelp(message)
return
}
switch cmd.Command {
case "poll":
if bot.CurrentPoll.Active {
bot.Reply(message,
fmt.Sprintf("Current Poll: %s, Yes=%d, No=%d",
bot.CurrentPoll.Text, bot.CurrentPoll.Yes, bot.CurrentPoll.No))
} else {
bot.Reply(message, "No poll currently active.")
}
default:
// see if it's a normal command // see if it's a normal command
reply, valid_cmd := CONFIG.Commands[strings.ToLower(cmd)] reply, valid_cmd := bot.Config.Commands[strings.ToLower(cmd.Command)]
if valid_cmd { if valid_cmd {
client.Reply(message.Channel, message.ID, reply) bot.Reply(message, reply)
} else if !valid_cmd { } else if !valid_cmd {
client.Reply(message.Channel, message.ID, "Invalid command. Use !help to see what's available.") bot.SendHelp(message)
} }
}) }
}
client.OnSelfJoinMessage(func (message twitch.UserJoinMessage) { func (bot *Bot) HandleMessage(message twitch.PrivateMessage) {
log.Println("Join", message.Channel, "as", message.User, "successful") // not sure if this is good enough for auth
}) if strings.HasPrefix(message.Message, "$") && message.User.IsBroadcaster {
bot.StreamerAction(message)
} else if bot.CurrentPoll.Active && bot.TryPoll(message) {
return
} else if strings.HasPrefix(message.Message, "!") {
bot.ViewerAction(message)
}
}
func (bot *Bot) CreateClient() {
// or client := twitch.NewAnonymousClient() for an anonymous user (no write capabilities)
bot.Client = twitch.NewClient(bot.Config.ClientNick, bot.Secrets.AccessToken)
client.OnConnect(func () { bot.Client.OnPrivateMessage(func (message twitch.PrivateMessage) {
log.Println("Connected. Joining", CONFIG.Channel) bot.HandleMessage(message)
client.Join(CONFIG.Channel)
}) })
client.OnPingMessage(func (message twitch.PingMessage) { bot.Client.OnSelfJoinMessage(func (message twitch.UserJoinMessage) {
log.Println("PING!", message.Message) log.Println("Join", message.Channel, "as", message.User, "successful")
}) })
client.OnPongMessage(func (message twitch.PongMessage) { bot.Client.OnConnect(func () {
log.Println("PONG!", message.Message) log.Println("Connected. Joining", bot.Config.Channel)
bot.Client.Join(bot.Config.Channel)
}) })
client.OnNoticeMessage(func (message twitch.NoticeMessage) { bot.Client.OnNoticeMessage(func (message twitch.NoticeMessage) {
log.Println("NOTICE", message.Channel, message.Message) log.Println("NOTICE", message.Channel, message.Message)
}) })
}
for _, perodic := range(CONFIG.Periodic) { func (bot *Bot) RunPeriodic() {
go RunPeriodic(perodic, CONFIG.Channel, client) for _, perodic := range(bot.Config.Periodic) {
go RunPeriodic(perodic, bot.Config.Channel, bot.Client)
} }
}
func (bot *Bot) Connect() {
log.Println("Connecting to Twitch...") log.Println("Connecting to Twitch...")
err := client.Connect() err := bot.Client.Connect()
if err != nil { log.Fatal(err) } if err != nil { log.Fatal(err) }
} }
func CreateBot() *Bot {
var bot Bot
bot.CurrentPoll.Active = false
bot.LoadConfigs()
bot.RefreshToken()
bot.CreateClient()
return &bot
}
func main() {
bot := CreateBot()
bot.RunPeriodic()
bot.Connect()
}

Loading…
Cancel
Save