Twitch Oauth implemented (by hand)
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
This commit is contained in:
parent
8dbe61da21
commit
b547819234
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"channels": ["iMartynOnTwitch"],
|
||||
"channels": ["karaokards"],
|
||||
"externalUrl": "karaokards.ing.martyn.berlin"
|
||||
}
|
|
@ -4,7 +4,6 @@ metadata:
|
|||
labels:
|
||||
run: kardbot
|
||||
name: kardbot
|
||||
namespace: karaokards
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
|
@ -70,4 +69,4 @@ spec:
|
|||
name: config
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: kkard-data
|
||||
claimName: kkard-data
|
||||
|
|
|
@ -6,20 +6,16 @@ metadata:
|
|||
name: karaokards
|
||||
spec:
|
||||
rules:
|
||||
- host: karaokards.ing.martyn.berlin
|
||||
- host: karaokards-dev.ing.martyn.berlin
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: karaokards
|
||||
servicePort: 80
|
||||
path: /nope
|
||||
- backend:
|
||||
serviceName: karaokards
|
||||
servicePort: 80
|
||||
path: /
|
||||
tls:
|
||||
- hosts:
|
||||
- karaokards.ing.martyn.berlin
|
||||
secretName: karaokards-cert
|
||||
- karaokards-dev.ing.martyn.berlin
|
||||
secretName: karaokards-dev-cert
|
||||
status:
|
||||
loadBalancer: {}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
scribble "github.com/nanobox-io/golang-scribble"
|
||||
)
|
||||
|
||||
const PSTFormat = "Jan 2 15:04:05 PST"
|
||||
const UTCFormat = "Jan 2 15:04:05 UTC"
|
||||
|
||||
// Regex for parsing connection messages
|
||||
//
|
||||
|
@ -49,19 +49,31 @@ type OAuthCred struct {
|
|||
ClientID string `json:"client_id,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigStruct struct {
|
||||
InitialChannels []string `json:"channels"`
|
||||
IrcOAuthPath string `json:"ircoauthpath,omitempty"`
|
||||
StringPath string `json:"authpath,omitempty"`
|
||||
DataPath string `json:"datapath,omitempty"`
|
||||
ExternalUrl string `json:"externalurl,omitempty"`
|
||||
AppOAuthPath string `json:"appoauthpath,omitempty"`
|
||||
}
|
||||
|
||||
type KardBot struct {
|
||||
Channel string
|
||||
conn net.Conn
|
||||
Credentials *OAuthCred
|
||||
IrcCredentials *OAuthCred
|
||||
AppCredentials *OAuthCred
|
||||
MsgRate time.Duration
|
||||
Name string
|
||||
Port string
|
||||
PrivatePath string
|
||||
IrcPrivatePath string
|
||||
AppPrivatePath string
|
||||
Server string
|
||||
startTime time.Time
|
||||
Prompts []string
|
||||
Database scribble.Driver
|
||||
ChannelData map[string]ChannelData
|
||||
Config ConfigStruct
|
||||
}
|
||||
|
||||
type ChannelData struct {
|
||||
|
@ -71,6 +83,7 @@ type ChannelData struct {
|
|||
ExtraStrings string `json:"extrastrings,omitempty"`
|
||||
JoinTime time.Time `json:"jointime"`
|
||||
ControlChannel bool
|
||||
HasLeft bool `json:"hasleft"`
|
||||
}
|
||||
|
||||
// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it
|
||||
|
@ -97,6 +110,17 @@ func (bb *KardBot) Disconnect() {
|
|||
rgb.YPrintf("[%s] Closed connection from %s! | Live for: %fs\n", TimeStamp(), bb.Server, upTime)
|
||||
}
|
||||
|
||||
// Look at the channels I'm actually in
|
||||
func (bb *KardBot) ActiveChannels() int {
|
||||
count := 0
|
||||
for _, channel := range bb.ChannelData {
|
||||
if !channel.HasLeft {
|
||||
count = count + 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Listens for and logs messages from chat. Responds to commands from the channel owner. The bot
|
||||
// continues until it gets disconnected, told to shutdown, or forcefully shutdown.
|
||||
func (bb *KardBot) HandleChat() error {
|
||||
|
@ -193,7 +217,7 @@ func (bb *KardBot) HandleChat() error {
|
|||
bb.Disconnect()
|
||||
return nil
|
||||
case "kcardadmin":
|
||||
magicCode := bb.readOrCreateChannelKey(channel)
|
||||
magicCode := bb.ReadOrCreateChannelKey(channel)
|
||||
rgb.CPrintf(
|
||||
"[%s] Magic code is %s - https://karaokards.ing.martyn.berlin/admin/%s/%s\n",
|
||||
TimeStamp(),
|
||||
|
@ -222,7 +246,7 @@ func (bb *KardBot) HandleChat() error {
|
|||
// Login to the IRC server
|
||||
func (bb *KardBot) Login() {
|
||||
rgb.YPrintf("[%s] Logging into #%s...\n", TimeStamp(), bb.Channel)
|
||||
bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n"))
|
||||
bb.conn.Write([]byte("PASS " + bb.IrcCredentials.Password + "\r\n"))
|
||||
bb.conn.Write([]byte("NICK " + bb.Name + "\r\n"))
|
||||
}
|
||||
|
||||
|
@ -247,23 +271,34 @@ func (bb *KardBot) JoinChannel(channels ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Reads from the private credentials file and stores the data in the bot's Credentials field.
|
||||
func (bb *KardBot) ReadCredentials() error {
|
||||
// Reads from the private credentials file and stores the data in the bot's appropriate Credentials field.
|
||||
func (bb *KardBot) ReadCredentials(credType string) error {
|
||||
|
||||
var err error
|
||||
var credFile []byte
|
||||
// reads from the file
|
||||
credFile, err := ioutil.ReadFile(bb.PrivatePath)
|
||||
if credType == "IRC" {
|
||||
credFile, err = ioutil.ReadFile(bb.IrcPrivatePath)
|
||||
} else {
|
||||
credFile, err = ioutil.ReadFile(bb.AppPrivatePath)
|
||||
}
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
bb.Credentials = &OAuthCred{}
|
||||
|
||||
// parses the file contents
|
||||
var creds OAuthCred
|
||||
dec := json.NewDecoder(strings.NewReader(string(credFile)))
|
||||
if err = dec.Decode(bb.Credentials); nil != err && io.EOF != err {
|
||||
if err = dec.Decode(&creds); nil != err && io.EOF != err {
|
||||
return err
|
||||
}
|
||||
|
||||
if credType == "IRC" {
|
||||
bb.IrcCredentials = &creds
|
||||
} else {
|
||||
bb.AppCredentials = &creds
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -322,7 +357,14 @@ func (bb *KardBot) Say(msg string, channels ...string) error {
|
|||
// pre-specified channel, and then handle the chat. It will attempt to reconnect until it is told to
|
||||
// shut down, or is forcefully shutdown.
|
||||
func (bb *KardBot) Start() {
|
||||
err := bb.ReadCredentials()
|
||||
err := bb.ReadCredentials("IRC")
|
||||
if nil != err {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Aborting!")
|
||||
return
|
||||
}
|
||||
|
||||
err = bb.ReadCredentials("App")
|
||||
if nil != err {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Aborting!")
|
||||
|
@ -340,8 +382,10 @@ func (bb *KardBot) Start() {
|
|||
bb.Connect()
|
||||
bb.Login()
|
||||
if len(bb.ChannelData) > 0 {
|
||||
for channelName := range bb.ChannelData {
|
||||
bb.JoinChannel(channelName)
|
||||
for channelName,channelData := range bb.ChannelData {
|
||||
if !channelData.HasLeft {
|
||||
bb.JoinChannel(channelName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bb.JoinChannel()
|
||||
|
@ -405,7 +449,7 @@ func (bb *KardBot) readChannelData() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (bb *KardBot) readOrCreateChannelKey(channel string) string {
|
||||
func (bb *KardBot) ReadOrCreateChannelKey(channel string) string {
|
||||
magicCode := ""
|
||||
var err error
|
||||
var record ChannelData
|
||||
|
@ -421,6 +465,7 @@ func (bb *KardBot) readOrCreateChannelKey(channel string) string {
|
|||
rgb.YPrintf("[%s] No channel key for #%s exists, creating one\n", TimeStamp(), channel)
|
||||
newuu, _ := uuid.NewRandom()
|
||||
magicCode = base64.StdEncoding.EncodeToString([]byte(newuu.String()))
|
||||
record.HasLeft = true
|
||||
record.AdminKey = magicCode
|
||||
if record.Name == "" {
|
||||
record.Name = channel
|
||||
|
@ -438,7 +483,7 @@ func (bb *KardBot) readOrCreateChannelKey(channel string) string {
|
|||
}
|
||||
|
||||
func TimeStamp() string {
|
||||
return TimeStampFmt(PSTFormat)
|
||||
return TimeStampFmt(UTCFormat)
|
||||
}
|
||||
|
||||
func TimeStampFmt(format string) string {
|
||||
|
|
|
@ -10,13 +10,40 @@ import (
|
|||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
//var store = sessions.NewCookieStore(os.Getenv("SESSION_KEY"))
|
||||
|
||||
type twitchauthresponse struct {
|
||||
Access_token string `json: "access_token"`
|
||||
Expires_in int `json: "expires_in"`
|
||||
Refresh_token string `json: "refresh_token"`
|
||||
Scope []string `json: "scope"`
|
||||
Token_type string `json: "token_type"`
|
||||
}
|
||||
|
||||
type twitchUser struct {
|
||||
Id string `json: "id"`
|
||||
Login string `json: "login"`
|
||||
Display_name string `json: "display_name"`
|
||||
Type string `json: "type"`
|
||||
Broadcaster_type string `json: "affiliate"`
|
||||
Description string `json: "description"`
|
||||
Profile_image_url string `json: "profile_image_url"`
|
||||
Offline_image_url string `json: "offline_image_url"`
|
||||
View_count int `json: "view_count"`
|
||||
}
|
||||
|
||||
type twitchUsersBigResponse struct {
|
||||
Data []twitchUser `json:"data"`
|
||||
}
|
||||
|
||||
var ircBot *irc.KardBot
|
||||
|
||||
func HealthHandler(response http.ResponseWriter, request *http.Request) {
|
||||
|
@ -49,6 +76,8 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
|
|||
AvailCount int
|
||||
ChannelCount int
|
||||
MessageCount int
|
||||
ClientID string
|
||||
BaseURI string
|
||||
}
|
||||
// tmpl, err := template.New("html"+request.URL.Path).Funcs(template.FuncMap{
|
||||
// "ToUpper": strings.ToUpper,
|
||||
|
@ -73,7 +102,7 @@ func TemplateHandler(response http.ResponseWriter, request *http.Request) {
|
|||
// NotFoundHandler(response, request)
|
||||
// return
|
||||
}
|
||||
var td = TemplateData{ircBot.Prompts[rand.Intn(len(ircBot.Prompts))], len(ircBot.Prompts), len(ircBot.ChannelData), 0}
|
||||
var td = TemplateData{ircBot.Prompts[rand.Intn(len(ircBot.Prompts))], len(ircBot.Prompts), ircBot.ActiveChannels(), 0, ircBot.AppCredentials.ClientID, "https://"+ircBot.Config.ExternalUrl}
|
||||
err = tmpl.Execute(response, td)
|
||||
if err != nil {
|
||||
http.Error(response, err.Error(), http.StatusInternalServerError)
|
||||
|
@ -97,22 +126,40 @@ func AdminHandler(response http.ResponseWriter, request *http.Request) {
|
|||
Command string
|
||||
ExtraStrings string
|
||||
SinceTime time.Time
|
||||
SinceTimeUTC string
|
||||
Leaving bool
|
||||
HasLeft bool
|
||||
}
|
||||
channelData := ircBot.ChannelData[vars["channel"]]
|
||||
var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, false}
|
||||
var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, channelData.JoinTime.Format(irc.UTCFormat), false, channelData.HasLeft}
|
||||
|
||||
if request.Method == "POST" {
|
||||
request.ParseForm()
|
||||
if strings.Join(request.PostForm["leave"], ",") == "Leave twitch channel" {
|
||||
td.Leaving = true
|
||||
} else if strings.Join(request.PostForm["reallyleave"], ",") == "Really leave twitch channel" {
|
||||
delete(ircBot.ChannelData, vars["channel"])
|
||||
ircBot.Database.Delete("channelData", vars["channel"])
|
||||
record := ircBot.ChannelData[vars["channel"]]
|
||||
record.HasLeft = true
|
||||
ircBot.ChannelData[vars["channel"]] = record
|
||||
ircBot.LeaveChannel(vars["channel"])
|
||||
ircBot.Database.Write("channelData", vars["channel"], record)
|
||||
LeaveHandler(response, request)
|
||||
return
|
||||
}
|
||||
if strings.Join(request.PostForm["join"], ",") == "Come on in" {
|
||||
record := ircBot.ChannelData[vars["channel"]]
|
||||
td.HasLeft = false
|
||||
record.Name = vars["channel"]
|
||||
record.JoinTime = time.Now()
|
||||
record.HasLeft = false
|
||||
if record.Command == "" {
|
||||
record.Command = "card"
|
||||
}
|
||||
ircBot.Database.Write("channelData", vars["channel"], record)
|
||||
ircBot.ChannelData[vars["channel"]] = record
|
||||
td = TemplateData{record.Name, record.Command, record.ExtraStrings, record.JoinTime, record.JoinTime.Format(irc.UTCFormat), false, record.HasLeft}
|
||||
ircBot.JoinChannel(record.Name)
|
||||
}
|
||||
sourceData := ircBot.ChannelData[vars["channel"]]
|
||||
if strings.Join(request.PostForm["Command"], ",") != "" {
|
||||
sourceData.Command = strings.Join(request.PostForm["Command"], ",")
|
||||
|
@ -137,6 +184,149 @@ func UnauthorizedHandler(response http.ResponseWriter, request *http.Request) {
|
|||
tmpl.Execute(response, nil)
|
||||
}
|
||||
|
||||
func twitchHTTPClient(call string, bearer string) (string,error) {
|
||||
url := "https://api.twitch.tv/helix/" + call
|
||||
var bearerHeader = "Bearer " + bearer
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req.Header.Add("Authorization", bearerHeader)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "",err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return string([]byte(body)), nil
|
||||
}
|
||||
|
||||
func TwitchAdminHandler(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
vars := mux.Vars(request)
|
||||
if (vars["code"] != "") {
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
resp, err := http.PostForm(
|
||||
"https://id.twitch.tv/oauth2/token",
|
||||
url.Values{
|
||||
"client_id": {ircBot.AppCredentials.ClientID},
|
||||
"client_secret": {ircBot.AppCredentials.Password},
|
||||
"code": {vars["code"]},
|
||||
"grant_type": {"authorization_code"},
|
||||
"redirect_uri": {"https://"+ircBot.Config.ExternalUrl+"/twitchadmin"}})
|
||||
if err != nil {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var oauthResponse twitchauthresponse
|
||||
err = json.Unmarshal(body, &oauthResponse)
|
||||
if err != nil {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
usersResponse, err := twitchHTTPClient("users", oauthResponse.Access_token)
|
||||
if err != nil {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var usersObject twitchUsersBigResponse
|
||||
err = json.Unmarshal([]byte(usersResponse), &usersObject)
|
||||
if err != nil {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(usersObject.Data) != 1 {
|
||||
response.WriteHeader(500)
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: Twitch returned not 1 user for the request!")
|
||||
return
|
||||
}
|
||||
|
||||
user := usersObject.Data[0]
|
||||
|
||||
magicCode := ircBot.ReadOrCreateChannelKey(user.Login)
|
||||
url := "https://"+ircBot.Config.ExternalUrl+"/admin/"+user.Login+"/"+magicCode
|
||||
http.Redirect(response, request, url, http.StatusFound)
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
fmt.Fprintf(response, "I'm not okay jack! %v \n", vars)
|
||||
for key, val := range(vars) {
|
||||
fmt.Fprint(response, "%s = %s\n", key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TwitchBackendHandler(response http.ResponseWriter, request *http.Request){
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
vars := mux.Vars(request)
|
||||
// fmt.Fprintf(response, "I'm okay jack! %v \n", vars)
|
||||
// for key, val := range(vars) {
|
||||
// fmt.Fprint(response, "%s = %s\n", key, val)
|
||||
// }
|
||||
|
||||
if (vars["code"] != "") {
|
||||
// https://id.twitch.tv/oauth2/token
|
||||
// ?client_id=<your client ID>
|
||||
// &client_secret=<your client secret>
|
||||
// &code=<authorization code received above>
|
||||
// &grant_type=authorization_code
|
||||
// &redirect_uri=<your registered redirect URI>
|
||||
|
||||
// ircBot.AppCredentials.ClientID
|
||||
// ircBot.AppCredentials.Password
|
||||
// vars["oauthtoken"]
|
||||
// authorization_code
|
||||
// "https://"+ircBot.Config.ExternalUrl+/twitchadmin
|
||||
fmt.Println("Asking twitch for more...")
|
||||
resp, err := http.PostForm(
|
||||
"https://id.twitch.tv/oauth2/token",
|
||||
url.Values{
|
||||
"client_id": {ircBot.AppCredentials.ClientID},
|
||||
"client_secret": {ircBot.AppCredentials.Password},
|
||||
"code": {vars["code"]},
|
||||
"grant_type": {"authorization_code"},
|
||||
"redirect_uri": {"https://"+ircBot.Config.ExternalUrl+"/twitchadmin"}})
|
||||
if err != nil {
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, "ERROR: "+err.Error())
|
||||
}
|
||||
response.Header().Add("Content-type", "text/plain")
|
||||
fmt.Fprint(response, string(body))
|
||||
} else {
|
||||
UnauthorizedHandler(response, request)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleHTTP(passedIrcBot *irc.KardBot) {
|
||||
ircBot = passedIrcBot
|
||||
r := mux.NewRouter()
|
||||
|
@ -148,6 +338,10 @@ func HandleHTTP(passedIrcBot *irc.KardBot) {
|
|||
r.PathPrefix("/static/").Handler(http.FileServer(http.Dir("./web/")))
|
||||
r.HandleFunc("/cover.css", CSSHandler)
|
||||
r.HandleFunc("/admin/{channel}/{key}", AdminHandler)
|
||||
//r.HandleFunc("/twitchadmin", TwitchAdminHandler)
|
||||
//r.HandleFunc("/twitchtobackend", TwitchBackendHandler)
|
||||
r.Path("/twitchtobackend").Queries("access_token","{access_token}","scope","{scope}","token_type","{token_type}").HandlerFunc(TwitchBackendHandler)
|
||||
r.Path("/twitchadmin").Queries("code","{code}","scope","{scope}").HandlerFunc(TwitchAdminHandler)
|
||||
http.Handle("/", r)
|
||||
srv := &http.Server{
|
||||
Handler: loggedRouter,
|
||||
|
|
72
main.go
72
main.go
|
@ -15,13 +15,6 @@ import (
|
|||
scribble "github.com/nanobox-io/golang-scribble"
|
||||
)
|
||||
|
||||
type configStruct struct {
|
||||
InitialChannels []string `json:"channels"`
|
||||
OAuthPath string `json:"oauthpath,omitempty"`
|
||||
StringPath string `json:"authpath,omitempty"`
|
||||
DataPath string `json:"datapath,omitempty"`
|
||||
}
|
||||
|
||||
type customStringsStruct struct {
|
||||
Strings []string `json:"strings,omitempty"`
|
||||
}
|
||||
|
@ -30,7 +23,7 @@ var selectablePrompts []string
|
|||
|
||||
var customStrings customStringsStruct
|
||||
|
||||
var config configStruct
|
||||
var config irc.ConfigStruct
|
||||
|
||||
func readConfig() {
|
||||
var data []byte
|
||||
|
@ -65,12 +58,13 @@ func readConfig() {
|
|||
rgb.RPrintf("[%s] Could not read `%s`. File reading error: %s\n", irc.TimeStamp(), configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = json.Unmarshal(data, &customStrings)
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
rgb.RPrintf("[%s] Could not unmarshal `%s`. Unmarshal error: %s\n", irc.TimeStamp(), configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rgb.YPrintf("[%s] Read config file from `%s`\n", irc.TimeStamp(), configFile)
|
||||
rgb.YPrintf("[%s] config %v\n", irc.TimeStamp(), config)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -102,8 +96,8 @@ func openDatabase() *scribble.Driver {
|
|||
dataPath = exPath + "/data"
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(config.OAuthPath); os.IsNotExist(err) {
|
||||
rgb.RPrintf("[%s] Error, config-specified path '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("KARAOKARDS_DATA_FOLDER"))
|
||||
if _, err := os.Stat(config.DataPath); os.IsNotExist(err) {
|
||||
rgb.RPrintf("[%s] Error, config-specified path '%s' doesn't exist!\n", irc.TimeStamp(), config.DataPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
dataPath = config.DataPath
|
||||
|
@ -167,44 +161,72 @@ func main() {
|
|||
selectablePrompts := append(selectablePrompts, dbGlobalPrompts...)
|
||||
|
||||
rgb.YPrintf("[%s] %d prompts available.\n", irc.TimeStamp(), len(selectablePrompts))
|
||||
oauthPath := ""
|
||||
if config.OAuthPath == "" {
|
||||
ircOauthPath := ""
|
||||
if config.IrcOAuthPath == "" {
|
||||
if os.Getenv("TWITCH_OAUTH_JSON") != "" {
|
||||
if _, err := os.Stat(os.Getenv("TWITCH_OAUTH_JSON")); os.IsNotExist(err) {
|
||||
os.Exit(1)
|
||||
}
|
||||
oauthPath = os.Getenv("TWITCH_OAUTH_JSON")
|
||||
ircOauthPath = os.Getenv("TWITCH_OAUTH_JSON")
|
||||
} else {
|
||||
if _, err := os.Stat(os.Getenv("HOME") + "/.twitch/oauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.TimeStamp(), os.Getenv("HOME")+"/.twitch/oauth.json", "/etc/twitch/oauth.json")
|
||||
if _, err := os.Stat("/etc/twitch/oauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", irc.TimeStamp(), "/etc/twitch/oauth.json")
|
||||
if _, err := os.Stat(os.Getenv("HOME") + "/.twitch/ircoauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.TimeStamp(), os.Getenv("HOME")+"/.twitch/ircoauth.json", "/etc/twitch/ircoauth.json")
|
||||
if _, err := os.Stat("/etc/twitch/ircoauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", irc.TimeStamp(), "/etc/twitch/ircoauth.json")
|
||||
os.Exit(1)
|
||||
}
|
||||
oauthPath = "/etc/twitch/oauth.json"
|
||||
ircOauthPath = "/etc/twitch/ircoauth.json"
|
||||
} else {
|
||||
oauthPath = os.Getenv("HOME") + "/.twitch/oauth.json"
|
||||
ircOauthPath = os.Getenv("HOME") + "/.twitch/ircoauth.json"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(config.OAuthPath); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error config-specified oauth file %s doesn't exist, bailing!\n", irc.TimeStamp(), config.OAuthPath)
|
||||
if _, err := os.Stat(config.IrcOAuthPath); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error config-specified oauth file %s doesn't exist, bailing!\n", irc.TimeStamp(), config.IrcOAuthPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
oauthPath = config.OAuthPath
|
||||
ircOauthPath = config.IrcOAuthPath
|
||||
}
|
||||
appOauthPath := ""
|
||||
if config.AppOAuthPath == "" {
|
||||
if os.Getenv("TWITCH_OAUTH_JSON") != "" {
|
||||
if _, err := os.Stat(os.Getenv("TWITCH_OAUTH_JSON")); os.IsNotExist(err) {
|
||||
os.Exit(1)
|
||||
}
|
||||
appOauthPath = os.Getenv("TWITCH_OAUTH_JSON")
|
||||
} else {
|
||||
if _, err := os.Stat(os.Getenv("HOME") + "/.twitch/appoauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.TimeStamp(), os.Getenv("HOME")+"/.twitch/appoauth.json", "/etc/twitch/appoauth.json")
|
||||
if _, err := os.Stat("/etc/twitch/appoauth.json"); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", irc.TimeStamp(), "/etc/twitch/appoauth.json")
|
||||
os.Exit(1)
|
||||
}
|
||||
appOauthPath = "/etc/twitch/appoauth.json"
|
||||
} else {
|
||||
appOauthPath = os.Getenv("HOME") + "/.twitch/appoauth.json"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(config.AppOAuthPath); os.IsNotExist(err) {
|
||||
rgb.YPrintf("[%s] Error config-specified oauth file %s doesn't exist, bailing!\n", irc.TimeStamp(), config.AppOAuthPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
appOauthPath = config.AppOAuthPath
|
||||
}
|
||||
|
||||
// Replace the channel name, bot name, and the path to the private directory with your respective
|
||||
// values.
|
||||
myBot := irc.KardBot{
|
||||
Channel: "imartynontwitch",
|
||||
Channel: "karaokards",
|
||||
MsgRate: time.Duration(20/30) * time.Millisecond,
|
||||
Name: "Karaokards",
|
||||
Port: "6667",
|
||||
PrivatePath: oauthPath,
|
||||
IrcPrivatePath: ircOauthPath,
|
||||
AppPrivatePath: appOauthPath,
|
||||
Server: "irc.chat.twitch.tv",
|
||||
Prompts: selectablePrompts,
|
||||
Database: *persistentData,
|
||||
Config: config,
|
||||
}
|
||||
go func() {
|
||||
rgb.YPrintf("[%s] Starting webserver on port %s\n", irc.TimeStamp(), "5353")
|
||||
|
|
|
@ -106,21 +106,28 @@
|
|||
</script>
|
||||
<h1 class="cover-heading">Karaokards admin panel for {{.Channel}}!!!</h1>
|
||||
<form method="POST">
|
||||
{{ if .Leaving }}
|
||||
<h2>Do you really want this bot to leave your channel? Right now you would have to ask Martyn to re-add it.</h2>
|
||||
<p><input id="leaveButton" type="submit" name="reallyleave" value="Really leave twitch channel"></p>
|
||||
{{ if .HasLeft }}
|
||||
<h2>Not in your channel at the moment!</h2>
|
||||
<p>The bot is not currently in your channel, chances are you've not ever asked it to join, you asked it to leave, or something went horribly wrong.</p>
|
||||
<p>You can invite the bot to your channel by clicking here : <input id="joinButton" type="submit" name="join" value="Come on in"></p>
|
||||
{{ else }}
|
||||
<table>
|
||||
<thead><tr><td>Channel Data :</td><td><input type="button" value="Edit" onclick="javascript:editMode();"></td></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Member of channel since {{.SinceTime}}</td></tr>
|
||||
<tr><td>Command for prompt:</td><td class="displaySetting"></tdclass>{{.Command}}</td><td class="editSetting"><input type="text" name="Command" value="{{.Command}}"></td></tr>
|
||||
<tr><td>Extra prompts (one per line):</td><td class="displaySetting">{{.ExtraStrings}}</td><td class="editSetting"><textarea name="ExtraStrings" >{{.ExtraStrings}}</textarea></td></tr>
|
||||
<tr><td> </td><td><input id="saveButton" type="submit" class="hiddenSave" name="save" value="Save changes"></td></tr>
|
||||
<tr id="yuhateme" class="hiddenSave"><td>Or... please don't go but...</td></tr>
|
||||
<tr><td><input id="leaveButton" type="submit" class="hiddenSave" name="leave" value="Leave twitch channel"></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{ if .Leaving }}
|
||||
<h2>Do you really want this bot to leave your channel?</h2>
|
||||
<p><input id="leaveButton" type="submit" name="reallyleave" value="Really leave twitch channel"></p>
|
||||
{{ else }}
|
||||
<h2>Note you can give your moderators the url you are on right now to control this bot. They don't have to be logged into twitch to do so.</h2>
|
||||
<table>
|
||||
<thead><tr><td>Channel Data :</td><td><input type="button" value="Edit" onclick="javascript:editMode();"></td></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Member of channel since {{.SinceTimeUTC}}</td></tr>
|
||||
<tr><td>Command for prompt:</td><td class="displaySetting"></tdclass>{{.Command}}</td><td class="editSetting"><input type="text" name="Command" value="{{.Command}}"></td></tr>
|
||||
<tr><td>Extra prompts (one per line):</td><td class="displaySetting">{{.ExtraStrings}}</td><td class="editSetting"><textarea name="ExtraStrings" >{{.ExtraStrings}}</textarea></td></tr>
|
||||
<tr><td> </td><td><input id="saveButton" type="submit" class="hiddenSave" name="save" value="Save changes"></td></tr>
|
||||
<tr id="yuhateme" class="hiddenSave"><td>Or... please don't go but...</td></tr>
|
||||
<tr><td><input id="leaveButton" type="submit" class="hiddenSave" name="leave" value="Leave twitch channel"></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</form>
|
||||
</main>
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
<h3 class="masthead-brand">Karaokards</h3>
|
||||
<nav class="nav nav-masthead justify-content-center">
|
||||
<a class="nav-link active" href="/">Home</a>
|
||||
<a class="nav-link active" href="https://id.twitch.tv/oauth2/authorize?client_id={{.ClientID}}&redirect_uri={{.BaseURI}}/twitchadmin&response_type=code&scope=user:read:broadcast">Admin - log in with twitch</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="Karaokards">
|
||||
<meta name="author" content="Martyn Ranyard">
|
||||
<title>Please Stand By!</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="/cover.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
li.match-nomatch{
|
||||
background-color: #1e2122;
|
||||
}
|
||||
li.match-matchtrack{
|
||||
background-color: #E9B000;
|
||||
}
|
||||
li.match-fullmatch{
|
||||
background-color: #008F95;
|
||||
}
|
||||
li.match-matchtrackfuzzt{
|
||||
background-color: darkgray;
|
||||
}
|
||||
li.match-fullmatchfuzzy{
|
||||
background-color: darkgray;
|
||||
}
|
||||
a{
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-center" onload=directToResults()>
|
||||
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
|
||||
<header class="masthead mb-auto">
|
||||
<div class="inner">
|
||||
<h3 class="masthead-brand">Karaokards</h3>
|
||||
<nav class="nav nav-masthead justify-content-center">
|
||||
<a class="nav-link active" href="/">Home</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main role="main" class="inner cover">
|
||||
<h1 class="cover-heading">Please stand by, twitch gives us stuff that needs to be sent to the server!</h1>
|
||||
<img src="https://media.giphy.com/media/ule4vhcY1xEKQ/source.gif" alt="Cats typing furiously" />
|
||||
<p/>
|
||||
<p>Just hold on, the javascript is doing it's stuff. Don't blame me, twitch really forces us to use javascript here.</p>
|
||||
<p>Shameless self-promotion : Follow me on twitch - <a href="https://www.twitch.tv/iMartynOnTwitch">iMartynOnTwitch</a>, oddly enough, I do a lot of twitchsings!</p>
|
||||
</main>
|
||||
<footer class="mastfoot mt-auto">
|
||||
<div class="inner">
|
||||
<p>Cover template for <a href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
// #access_token=hkq5diaiopu23tzyo5oik7jl7g2w0n&scope=user%3Aread%3Abroadcast&token_type=bearer
|
||||
|
||||
function directToResults() {
|
||||
var url = document.createElement('a');
|
||||
url.setAttribute("href", window.location.href);
|
||||
if ((url.port != 80) && (url.port != 443)) {
|
||||
customPort = ":"+url.port
|
||||
} else {
|
||||
customPort = ""
|
||||
}
|
||||
|
||||
u = new URLSearchParams(document.location.hash.substr(1))
|
||||
var destination = new URL(url.protocol + "//" + url.hostname + customPort + "/twitchtobackend?" + u.toString())
|
||||
console.log(destination)
|
||||
window.location.href = destination
|
||||
}
|
||||
|
||||
function toggleUnfound() {
|
||||
var unmatched = document.getElementsByClassName('match-nomatch'), i;
|
||||
if (document.getElementById("showhidebutton").getAttribute("tracksHidden") != "true") {
|
||||
document.getElementById("showhidebutton").setAttribute("tracksHidden","true")
|
||||
for (i = 0; i < unmatched.length; i += 1) {
|
||||
unmatched[i].style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
document.getElementById("showhidebutton").setAttribute("tracksHidden","false")
|
||||
for (i = 0; i < unmatched.length; i += 1) {
|
||||
unmatched[i].style.display = 'list-item';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue