You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
12 KiB
360 lines
12 KiB
package webserver |
|
|
|
import ( |
|
"math/rand" |
|
|
|
irc "git.martyn.berlin/martyn/karaokards/internal/irc" |
|
"github.com/gorilla/handlers" |
|
"github.com/gorilla/mux" |
|
|
|
"encoding/json" |
|
"fmt" |
|
"html/template" |
|
"io/ioutil" |
|
"net/http" |
|
"net/url" |
|
"os" |
|
"strings" |
|
"time" |
|
) |
|
|
|
//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) { |
|
response.Header().Add("Content-type", "text/plain") |
|
fmt.Fprint(response, "I'm okay jack!") |
|
} |
|
|
|
func NotFoundHandler(response http.ResponseWriter, request *http.Request) { |
|
response.Header().Add("X-Template-File", "html"+request.URL.Path) |
|
response.WriteHeader(404) |
|
tmpl := template.Must(template.ParseFiles("web/404.html")) |
|
tmpl.Execute(response, nil) |
|
} |
|
|
|
func CSSHandler(response http.ResponseWriter, request *http.Request) { |
|
response.Header().Add("Content-type", "text/css") |
|
tmpl := template.Must(template.ParseFiles("web/cover.css")) |
|
tmpl.Execute(response, nil) |
|
} |
|
|
|
func RootHandler(response http.ResponseWriter, request *http.Request) { |
|
request.URL.Path = "/index.html" |
|
TemplateHandler(response, request) |
|
} |
|
|
|
func TemplateHandler(response http.ResponseWriter, request *http.Request) { |
|
response.Header().Add("X-Template-File", "web"+request.URL.Path) |
|
type TemplateData struct { |
|
Prompt string |
|
AvailCount int |
|
ChannelCount int |
|
MessageCount int |
|
ClientID string |
|
BaseURI string |
|
} |
|
// tmpl, err := template.New("html"+request.URL.Path).Funcs(template.FuncMap{ |
|
// "ToUpper": strings.ToUpper, |
|
// "ToLower": strings.ToLower, |
|
// }).ParseFiles("html"+request.URL.Path) |
|
_ = strings.ToLower("Hello") |
|
if strings.Index(request.URL.Path, "/") < 0 { |
|
http.Error(response, "No slashes wat - "+request.URL.Path, http.StatusInternalServerError) |
|
return |
|
} |
|
|
|
basenameSlice := strings.Split(request.URL.Path, "/") |
|
basename := basenameSlice[len(basenameSlice)-1] |
|
//fmt.Fprintf(response, "%q", basenameSlice) |
|
tmpl, err := template.New(basename).Funcs(template.FuncMap{ |
|
"ToUpper": strings.ToUpper, |
|
"ToLower": strings.ToLower, |
|
}).ParseFiles("web" + request.URL.Path) |
|
if err != nil { |
|
http.Error(response, err.Error(), http.StatusInternalServerError) |
|
return |
|
// NotFoundHandler(response, request) |
|
// return |
|
} |
|
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) |
|
return |
|
} |
|
} |
|
|
|
func LeaveHandler(response http.ResponseWriter, request *http.Request) { |
|
request.URL.Path = "/bye.html" |
|
TemplateHandler(response, request) |
|
} |
|
|
|
func AdminHandler(response http.ResponseWriter, request *http.Request) { |
|
vars := mux.Vars(request) |
|
if vars["key"] != ircBot.ChannelData[vars["channel"]].AdminKey { |
|
UnauthorizedHandler(response, request) |
|
return |
|
} |
|
type TemplateData struct { |
|
Channel string |
|
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, 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" { |
|
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"], ",") |
|
td.Command = sourceData.Command |
|
ircBot.ChannelData[vars["channel"]] = sourceData |
|
} |
|
if strings.Join(request.PostForm["ExtraStrings"], ",") != sourceData.ExtraStrings { |
|
sourceData.ExtraStrings = strings.Join(request.PostForm["ExtraStrings"], ",") |
|
td.ExtraStrings = sourceData.ExtraStrings |
|
ircBot.ChannelData[vars["channel"]] = sourceData |
|
} |
|
ircBot.Database.Write("channelData", vars["channel"], sourceData) |
|
} |
|
tmpl := template.Must(template.ParseFiles("web/admin.html")) |
|
tmpl.Execute(response, td) |
|
} |
|
|
|
func UnauthorizedHandler(response http.ResponseWriter, request *http.Request) { |
|
response.Header().Add("X-Template-File", "html"+request.URL.Path) |
|
response.WriteHeader(401) |
|
tmpl := template.Must(template.ParseFiles("web/401.html")) |
|
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!\n---\n") |
|
fmt.Fprint(response, usersObject.Data) |
|
fmt.Fprint(response, "\n---\n") |
|
fmt.Fprint(response, body) |
|
fmt.Fprint(response, "\n---\n") |
|
fmt.Fprint(response, usersResponse) |
|
fmt.Fprint(response, "\n---\n") |
|
fmt.Fprint(response, usersObject) |
|
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() |
|
loggedRouter := handlers.LoggingHandler(os.Stdout, r) |
|
r.NotFoundHandler = http.HandlerFunc(NotFoundHandler) |
|
r.HandleFunc("/", RootHandler) |
|
r.HandleFunc("/healthz", HealthHandler) |
|
r.HandleFunc("/web/{.*}", TemplateHandler) |
|
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, |
|
Addr: "0.0.0.0:5353", |
|
WriteTimeout: 15 * time.Second, |
|
ReadTimeout: 15 * time.Second, |
|
} |
|
fmt.Println("Listening on 0.0.0.0:5353") |
|
srv.ListenAndServe() |
|
}
|
|
|