This repository has been archived on 2022-01-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gidbig/webserver.go
T

333 lines
8.9 KiB
Go
Raw Permalink Normal View History

2017-01-08 20:15:09 +01:00
package main
import (
"bufio"
2017-01-09 21:34:37 +01:00
"crypto/rand"
"encoding/base64"
2021-04-14 16:26:47 +02:00
"errors"
2017-01-08 20:15:09 +01:00
"fmt"
2017-01-09 21:34:37 +01:00
"html/template"
2021-04-14 16:26:47 +02:00
"net"
2017-01-08 20:15:09 +01:00
"net/http"
2017-01-24 23:06:06 +01:00
_ "net/http/pprof"
"os"
2021-04-14 16:26:47 +02:00
"strconv"
2017-01-08 20:15:09 +01:00
"github.com/bwmarrin/discordgo"
2017-01-09 21:34:37 +01:00
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
2021-04-14 16:26:47 +02:00
"github.com/simplesurance/go-ip-anonymizer/ipanonymizer"
log "github.com/sirupsen/logrus"
2017-01-08 20:15:09 +01:00
"golang.org/x/oauth2"
)
2017-01-09 21:34:37 +01:00
const (
2017-01-19 22:49:17 +01:00
header = "templates/header.html"
footer = "templates/footer.html"
templateDir = "templates/"
2017-01-09 21:34:37 +01:00
)
2017-01-08 20:15:09 +01:00
var (
discordOauthConfig = &oauth2.Config{
RedirectURL: "",
ClientID: "",
ClientSecret: "",
2017-01-09 21:34:37 +01:00
Scopes: []string{"identify", "guilds"},
Endpoint: *endp,
2017-01-08 20:15:09 +01:00
}
// Some random string, random for each request
2017-01-09 21:34:37 +01:00
oauthStateString string
2017-01-08 20:15:09 +01:00
endp = &oauth2.Endpoint{
AuthURL: "https://discordapp.com/api/oauth2/authorize",
TokenURL: "https://discordapp.com/api/oauth2/token",
}
2017-01-09 21:34:37 +01:00
tmpls = map[string]*template.Template{}
2021-04-14 16:26:47 +02:00
2017-01-09 21:34:37 +01:00
// TODO change secret
store *sessions.CookieStore
2021-04-14 16:26:47 +02:00
ipAnonymizer = ipanonymizer.NewWithMask(
net.CIDRMask(16, 32),
net.CIDRMask(64, 128),
)
2017-01-08 20:15:09 +01:00
)
// SoundItem is used to represent a sound of our COLLECTIONS for html generation
2017-01-19 22:49:17 +01:00
type SoundItem struct {
Itemprefix string
2017-01-19 22:49:17 +01:00
Itemcommand string
Itemsoundname string
Itemtext string
Itemshorttext string
2017-01-19 22:49:17 +01:00
}
2017-01-09 21:34:37 +01:00
// startWebServer
func startWebServer(port string, ci string, cs string, redirectURL string) {
2017-01-19 22:49:17 +01:00
tmpls["home.html"] = template.Must(template.ParseFiles(templateDir+"home.html", header, footer))
tmpls["internal.html"] = template.Must(template.ParseFiles(templateDir+"internal.html", header, footer))
tmpls["item.html"] = template.Must(template.ParseFiles(templateDir + "item.html"))
tmpls["itemrowstart.html"] = template.Must(template.ParseFiles(templateDir + "itemrowstart.html"))
tmpls["itemrowend.html"] = template.Must(template.ParseFiles(templateDir + "itemrowend.html"))
2017-02-02 17:29:11 +01:00
tmpls["collwrapstart.html"] = template.Must(template.ParseFiles(templateDir + "collwrapstart.html"))
tmpls["collwrapend.html"] = template.Must(template.ParseFiles(templateDir + "collwrapend.html"))
2017-01-09 21:34:37 +01:00
store = sessions.NewCookieStore([]byte(cs))
2017-01-08 20:15:09 +01:00
discordOauthConfig.ClientID = ci
discordOauthConfig.ClientSecret = cs
discordOauthConfig.RedirectURL = redirectURL + "/discordCallback"
2017-01-09 21:34:37 +01:00
r := mux.NewRouter()
r.HandleFunc("/", handleMain)
r.HandleFunc("/logout", handleLogout)
2021-04-14 16:26:47 +02:00
r.HandleFunc("/discordLogin", handleDiscordLogin)
r.HandleFunc("/discordCallback", handleDiscordCallback)
r.HandleFunc("/playsound", handlePlaySound)
2017-01-09 21:34:37 +01:00
http.Handle("/", r)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
2017-01-09 21:34:37 +01:00
http.ListenAndServe(":"+port, nil)
2017-01-08 20:15:09 +01:00
}
func handlePlaySound(w http.ResponseWriter, r *http.Request) {
2021-04-14 16:26:47 +02:00
log.Infoln("WebUI /playsound Request from " + r.RemoteAddr)
r.ParseForm()
sound, soundCollection := findSoundAndCollection(r.FormValue("command"), r.FormValue("soundname"))
session, _ := store.Get(r, "gidbig-session")
var guild *discordgo.Guild
user, _ := discord.User(session.Values["discordUserID"].(string))
for _, g := range discord.State.Guilds {
for _, vs := range g.VoiceStates {
if vs.UserID == session.Values["discordUserID"].(string) {
guild = g
}
}
}
2017-02-06 15:02:33 +01:00
if user != nil && guild != nil && soundCollection != nil {
2017-02-06 14:58:03 +01:00
if sound != nil {
2017-08-03 18:50:20 +02:00
if sound.Name == "clearqueue" && soundCollection.Prefix == "1" {
2017-08-03 18:51:49 +02:00
clearQueue(user)
2017-08-03 18:50:20 +02:00
} else {
go enqueuePlay(user, guild, soundCollection, sound)
}
2017-02-06 14:58:03 +01:00
} else {
go enqueuePlay(user, guild, soundCollection, soundCollection.Random())
}
http.Error(w, http.StatusText(200), 200)
} else {
http.Error(w, http.StatusText(500), 500)
2017-02-02 17:29:11 +01:00
}
2017-02-06 14:58:03 +01:00
}
2017-01-08 20:15:09 +01:00
func handleMain(w http.ResponseWriter, r *http.Request) {
2021-04-14 16:26:47 +02:00
logWebRequests(r)
session, _ := store.Get(r, "gidbig-session")
2017-01-09 21:34:37 +01:00
if session.Values["discordUsername"] != nil {
2017-02-06 14:58:03 +01:00
var prefixes []string
var si []SoundItem
2017-01-19 22:49:17 +01:00
for _, sc := range COLLECTIONS {
2021-04-14 16:26:47 +02:00
newSoundItemRandom := SoundItem{
2017-02-02 17:29:11 +01:00
Itemprefix: sc.Prefix,
Itemcommand: "!" + sc.Prefix,
Itemsoundname: "",
Itemtext: "random",
Itemshorttext: "random",
}
2017-02-06 14:58:03 +01:00
prefixes = append(prefixes, sc.Prefix)
2017-02-02 17:29:11 +01:00
si = append(si, newSoundItemRandom)
2017-01-19 22:49:17 +01:00
for _, snd := range sc.Sounds {
2021-04-14 16:26:47 +02:00
newSoundItem := SoundItem{
Itemprefix: sc.Prefix,
Itemcommand: "!" + sc.Prefix,
Itemsoundname: snd.Name,
Itemtext: "!" + sc.Prefix + " " + snd.Name,
Itemshorttext: "!" + sc.Prefix + " " + snd.Name,
}
file, e := os.Open(fmt.Sprintf("audio/%v_%v.txt", sc.Prefix, snd.Name))
if e == nil {
scanner := bufio.NewScanner(file)
scanner.Scan()
text := scanner.Text()
newSoundItem.Itemtext = text
if len(text) > 20 {
text = text[0:20]
text += "..."
}
newSoundItem.Itemshorttext = text
}
si = append(si, newSoundItem)
2017-01-19 22:49:17 +01:00
}
}
2017-02-06 14:58:03 +01:00
err := tmpls["internal.html"].ExecuteTemplate(w, "header", prefixes)
if err != nil {
fmt.Println(err)
return
}
2017-02-02 17:29:11 +01:00
var currentPrefix string = ""
2017-02-02 17:52:09 +01:00
var i int = 0
for _, snd := range si {
2017-02-02 17:29:11 +01:00
if snd.Itemprefix != currentPrefix {
if i != 0 {
2017-02-02 17:52:09 +01:00
err = tmpls["itemrowend.html"].Execute(w, nil)
if err != nil {
fmt.Println(err)
return
}
2017-02-02 17:29:11 +01:00
err = tmpls["collwrapend.html"].Execute(w, nil)
if err != nil {
fmt.Println(err)
return
}
2017-02-02 17:52:09 +01:00
i = 0
2017-02-02 17:29:11 +01:00
}
err = tmpls["collwrapstart.html"].Execute(w, snd)
if err != nil {
fmt.Println(err)
return
}
currentPrefix = snd.Itemprefix
}
if i%4 == 0 {
err = tmpls["itemrowstart.html"].Execute(w, nil)
if err != nil {
fmt.Println(err)
return
}
}
err = tmpls["item.html"].Execute(w, snd)
2017-01-19 22:49:17 +01:00
if err != nil {
fmt.Println(err)
return
}
if i%4 == 3 {
err = tmpls["itemrowend.html"].Execute(w, nil)
if err != nil {
fmt.Println(err)
return
}
}
2017-02-02 17:52:09 +01:00
i++
2017-01-19 22:49:17 +01:00
}
err = tmpls["internal.html"].ExecuteTemplate(w, "footer", map[string]interface{}{})
if err != nil {
fmt.Println(err)
return
}
2017-01-09 21:34:37 +01:00
return
}
2017-01-19 22:49:17 +01:00
tmpls["home.html"].ExecuteTemplate(w, "header", map[string]interface{}{})
tmpls["home.html"].ExecuteTemplate(w, "footer", map[string]interface{}{})
2017-01-09 21:34:37 +01:00
}
func handleLogout(w http.ResponseWriter, r *http.Request) {
2021-04-14 16:26:47 +02:00
log.Infoln("WebUI /logout Request from " + r.RemoteAddr)
2017-01-09 21:34:37 +01:00
cookie := &http.Cookie{
Name: "gidbig-session",
2017-01-09 21:34:37 +01:00
Value: "",
Path: "/",
MaxAge: -1,
}
http.SetCookie(w, cookie)
http.Redirect(w, r, "/", http.StatusFound)
2017-01-08 20:15:09 +01:00
}
2021-04-14 16:26:47 +02:00
func handleDiscordLogin(w http.ResponseWriter, r *http.Request) {
logWebRequests(r)
2017-01-09 21:34:37 +01:00
b := make([]byte, 16)
rand.Read(b)
oauthStateString = base64.URLEncoding.EncodeToString(b)
session, _ := store.Get(r, "gidbig-session")
2017-01-09 21:34:37 +01:00
session.Values["state"] = oauthStateString
session.Save(r, w)
2017-01-08 20:15:09 +01:00
url := discordOauthConfig.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
2021-04-14 16:26:47 +02:00
func handleDiscordCallback(w http.ResponseWriter, r *http.Request) {
logWebRequests(r)
session, err := store.Get(r, "gidbig-session")
2017-01-09 21:34:37 +01:00
if err != nil {
fmt.Fprintln(w, "aborted")
return
}
if r.URL.Query().Get("state") != session.Values["state"] {
fmt.Fprintln(w, "no state match; possible csrf OR cookies not enabled")
return
}
2017-01-08 20:15:09 +01:00
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
2017-01-09 21:34:37 +01:00
token, err := discordOauthConfig.Exchange(oauth2.NoContext, r.FormValue("code"))
2017-01-08 20:15:09 +01:00
if err != nil {
2017-01-09 21:34:37 +01:00
fmt.Fprintln(w, "Code exchange (could not get token) failed with ", err)
return
}
if !token.Valid() {
fmt.Fprintln(w, "retrieved invalid token")
2017-01-08 20:15:09 +01:00
return
}
dg, err := discordgo.New("Bearer " + token.AccessToken)
if err != nil {
fmt.Println("Error while creating discord session: ", err)
return
}
user, _ := dg.User("@me")
2017-01-09 21:34:37 +01:00
session.Values["discordUserID"] = user.ID
session.Values["discordUsername"] = user.Username
session.Values["accessToken"] = token.AccessToken
session.Save(r, w)
dg.Close()
2017-01-09 21:34:37 +01:00
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
2017-01-08 20:15:09 +01:00
}
2021-04-14 16:26:47 +02:00
func parseIPPort(s string) (ip net.IP, port, space string, err error) {
ip = net.ParseIP(s)
if ip == nil {
var host string
host, port, err = net.SplitHostPort(s)
if err != nil {
return
}
if port != "" {
// This check only makes sense if service names are not allowed
if _, err = strconv.ParseUint(port, 10, 16); err != nil {
return
}
}
ip = net.ParseIP(host)
}
if ip == nil {
err = errors.New("invalid address format")
} else {
space = "IPv6"
if ip4 := ip.To4(); ip4 != nil {
space = "IPv4"
ip = ip4
}
}
return
}
func logWebRequests(r *http.Request) {
ip, port, _, err := parseIPPort(r.RemoteAddr)
if err != nil {
log.Warnln("Error parsing IP address for WebUI Request to " + r.RequestURI)
}
anonIP, err := ipAnonymizer.IPString(ip.String())
if err != nil {
log.Warnln("Could not anonymize IP address for WebUI Request to " + r.RequestURI)
} else {
log.Infoln("WebUI Request to " + r.RequestURI + " from " + anonIP + port)
}
}