|
|
|
|
@ -6,12 +6,18 @@ import ( |
|
|
|
|
"strings" |
|
|
|
|
"encoding/json" |
|
|
|
|
"time" |
|
|
|
|
"net/http" |
|
|
|
|
"net/url" |
|
|
|
|
"fmt" |
|
|
|
|
"flag" |
|
|
|
|
|
|
|
|
|
"github.com/gempir/go-twitch-irc/v4" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
type Secret struct { |
|
|
|
|
ClientID string |
|
|
|
|
ClientSecret string |
|
|
|
|
AccessToken string |
|
|
|
|
RefreshToken string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -24,9 +30,18 @@ type Config struct { |
|
|
|
|
ClientNick string |
|
|
|
|
Channel string |
|
|
|
|
Commands map[string]string |
|
|
|
|
Actions map[string]string |
|
|
|
|
Periodic []Periodic |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type TwitchTokenResponse struct { |
|
|
|
|
AccessToken string `json:"access_token"` |
|
|
|
|
RefreshToken string `json:"refresh_token"` |
|
|
|
|
ExpiresIn int `json:"expires_in"` |
|
|
|
|
Scope []string `json:"scope"` |
|
|
|
|
TokenType string `json:"token_type"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func LoadJSON[T any](file string) T { |
|
|
|
|
data, err := os.ReadFile(file) |
|
|
|
|
if err != nil { log.Fatal(err) } |
|
|
|
|
@ -45,24 +60,83 @@ func RunPeriodic(message Periodic, channel string, client *twitch.Client) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func RefreshToken(secrets* Secret) { |
|
|
|
|
api_url := "https://id.twitch.tv/oauth2/token" |
|
|
|
|
data := url.Values{} |
|
|
|
|
data.Set("client_id", secrets.ClientID) |
|
|
|
|
data.Set("client_secret", secrets.ClientSecret) |
|
|
|
|
data.Set("grant_type", "refresh_token") |
|
|
|
|
data.Set("refresh_token", secrets.RefreshToken) |
|
|
|
|
|
|
|
|
|
encoded := strings.NewReader(data.Encode()) |
|
|
|
|
resp, err := http.Post(api_url,"application/x-www-form-urlencoded", encoded) |
|
|
|
|
|
|
|
|
|
if err != nil { log.Fatal(err) } |
|
|
|
|
|
|
|
|
|
defer resp.Body.Close() |
|
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK { |
|
|
|
|
log.Fatal(fmt.Sprintf("Invalid status code: %v.", resp.StatusCode)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var token_resp TwitchTokenResponse |
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&token_resp) |
|
|
|
|
if err != nil { log.Fatal(err) } |
|
|
|
|
|
|
|
|
|
secrets.AccessToken = fmt.Sprintf("oauth:%s", token_resp.AccessToken); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func LoadConfigs() (Secret, Config) { |
|
|
|
|
var secrets_file string |
|
|
|
|
var config_file string |
|
|
|
|
var action_file string |
|
|
|
|
|
|
|
|
|
flag.StringVar(&secrets_file, "secrets", "secret.json", "Your secrets file. KEEP PRIVATE!") |
|
|
|
|
flag.StringVar(&config_file, "config", "config.json", "Your config.json. Can be public.") |
|
|
|
|
flag.StringVar(&action_file, "actions", "actions.json", "Your URL actions. KEEP PRIVATE!") |
|
|
|
|
flag.Parse() |
|
|
|
|
|
|
|
|
|
log.Println("Loading secrets from", secrets_file) |
|
|
|
|
log.Println("Loading config from", config_file) |
|
|
|
|
log.Println("Loading actions from", action_file) |
|
|
|
|
|
|
|
|
|
secrets := LoadJSON[Secret](secrets_file) |
|
|
|
|
config := LoadJSON[Config](config_file) |
|
|
|
|
config.Actions = LoadJSON[map[string]string](action_file) |
|
|
|
|
|
|
|
|
|
return secrets, config |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func main() { |
|
|
|
|
SECRETS := LoadJSON[Secret]("secret.json") |
|
|
|
|
CONFIG := LoadJSON[Config]("config.json") |
|
|
|
|
SECRETS, CONFIG := LoadConfigs() |
|
|
|
|
|
|
|
|
|
RefreshToken(&SECRETS) |
|
|
|
|
|
|
|
|
|
// or client := twitch.NewAnonymousClient() for an anonymous user (no write capabilities)
|
|
|
|
|
client := twitch.NewClient(CONFIG.ClientNick, SECRETS.ClientSecret) |
|
|
|
|
client := twitch.NewClient(CONFIG.ClientNick, SECRETS.AccessToken) |
|
|
|
|
|
|
|
|
|
client.OnPrivateMessage(func(message twitch.PrivateMessage) { |
|
|
|
|
// not sure if this is good enough for auth
|
|
|
|
|
|
|
|
|
|
cmd, found := strings.CutPrefix(message.Message, "!") |
|
|
|
|
if !found { return } |
|
|
|
|
|
|
|
|
|
// see if it's a valid action
|
|
|
|
|
action, is_action := CONFIG.Actions[strings.ToLower(cmd)] |
|
|
|
|
|
|
|
|
|
if is_action && message.User.IsBroadcaster { |
|
|
|
|
log.Println("Hitting URL", action) |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// see if it's a normal command
|
|
|
|
|
reply, valid_cmd := CONFIG.Commands[strings.ToLower(cmd)] |
|
|
|
|
|
|
|
|
|
if found { |
|
|
|
|
if valid_cmd { |
|
|
|
|
client.Reply(message.Channel, message.ID, reply) |
|
|
|
|
} else if !valid_cmd { |
|
|
|
|
client.Reply(message.Channel, message.ID, "Invalid command. Use !help to see what's available.") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
client.OnSelfJoinMessage(func (message twitch.UserJoinMessage) { |
|
|
|
|
|