From 46a0f5a1873b270285f275fc1c747031a1b6a24a Mon Sep 17 00:00:00 2001 From: Justin Hawkins Date: Sun, 31 Jan 2021 17:53:32 +1030 Subject: [PATCH] Rework configuration to it's own package and make it available to web server. Start a template driven web interface. --- build-release.pl | 4 +- config/config.go | 13 +++++ data/config.html | 8 +++ data/dau.css | 106 +++++++++++++++++++++++++++++++++++++ data/index.html | 9 +++- data/wrapper.tmpl | 58 ++++++++++++++++++++ dau.go | 86 ++++++++++++------------------ web/server.go | 132 ++++++++++++++++++++++++++++++++++------------ 8 files changed, 326 insertions(+), 90 deletions(-) create mode 100644 config/config.go create mode 100644 data/config.html create mode 100644 data/dau.css create mode 100644 data/wrapper.tmpl diff --git a/build-release.pl b/build-release.pl index d6d4c26..b9a9a4c 100755 --- a/build-release.pl +++ b/build-release.pl @@ -3,11 +3,11 @@ use strict; use warnings; -open my $fh, "<", "dau.go" || die $!; +open my $fh, "<", "config/config.go" || die $!; my $version; while (<$fh>) { - $version = $1 if /^const\s+currentVersion.*?"([\d\.]+)"/; + $version = $1 if /^const\s+CurrentVersion.*?"([\d\.]+)"/; } close $fh; diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..1e8e82a --- /dev/null +++ b/config/config.go @@ -0,0 +1,13 @@ +package config + +// Config for the application +var Config struct { + WebHookURL string + Path string + Watch int + Username string + NoWatermark bool + Exclude string +} + +const CurrentVersion string = "0.6" diff --git a/data/config.html b/data/config.html new file mode 100644 index 0000000..fb7a0d0 --- /dev/null +++ b/data/config.html @@ -0,0 +1,8 @@ + +
+

Config

+

Hey look it's DAU.

+

+ Learn more +

+
diff --git a/data/dau.css b/data/dau.css new file mode 100644 index 0000000..7c6d33c --- /dev/null +++ b/data/dau.css @@ -0,0 +1,106 @@ +/* + * Globals + */ + +/* Links */ +a, +a:focus, +a:hover { + color: #fff; +} + +/* Custom default button */ +.btn-secondary, +.btn-secondary:hover, +.btn-secondary:focus { + color: #333; + text-shadow: none; /* Prevent inheritance from `body` */ + background-color: #fff; + border: .05rem solid #fff; +} + + +/* + * Base structure + */ + +html, +body { + height: 100%; + background-color: #333; +} + +body { + display: -ms-flexbox; + display: flex; + color: #fff; + text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); + box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5); +} + +.cover-container { + max-width: 42em; +} + + +/* + * Header + */ +.masthead { + margin-bottom: 2rem; +} + +.masthead-brand { + margin-bottom: 0; +} + +.nav-masthead .nav-link { + padding: .25rem 0; + font-weight: 700; + color: rgba(255, 255, 255, .5); + background-color: transparent; + border-bottom: .25rem solid transparent; +} + +.nav-masthead .nav-link:hover, +.nav-masthead .nav-link:focus { + border-bottom-color: rgba(255, 255, 255, .25); +} + +.nav-masthead .nav-link + .nav-link { + margin-left: 1rem; +} + +.nav-masthead .active { + color: #fff; + border-bottom-color: #fff; +} + +@media (min-width: 48em) { + .masthead-brand { + float: left; + } + .nav-masthead { + float: right; + } +} + + +/* + * Cover + */ +.cover { + padding: 0 1.5rem; +} +.cover .btn-lg { + padding: .75rem 1.25rem; + font-weight: 700; +} + + +/* + * Footer + */ +.mastfoot { + color: rgba(255, 255, 255, .5); +} diff --git a/data/index.html b/data/index.html index 46c9a48..1a3cc09 100644 --- a/data/index.html +++ b/data/index.html @@ -1 +1,8 @@ -THIS IS SPARTA + +
+

Discord Auto Upload

+

Hey look it's DAU.

+

+ Learn more +

+
diff --git a/data/wrapper.tmpl b/data/wrapper.tmpl new file mode 100644 index 0000000..c65cc58 --- /dev/null +++ b/data/wrapper.tmpl @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + +
+
+
+

discord-auto-upload ({{.Version}})

+ +
+
+ +{{.Body}} + + +
+ + + + diff --git a/dau.go b/dau.go index 7e89a40..f29546c 100644 --- a/dau.go +++ b/dau.go @@ -24,58 +24,39 @@ import ( "github.com/fogleman/gg" "github.com/pborman/getopt" - "github.com/skratchdot/open-golang/open" + // "github.com/skratchdot/open-golang/open" "golang.org/x/image/font/inconsolata" "github.com/tardisx/discord-auto-upload/web" + "github.com/tardisx/discord-auto-upload/config" ) -const currentVersion = "0.7" - var lastCheck = time.Now() var newLastCheck = time.Now() -// Config for the application -type Config struct { - webhookURL string - path string - watch int - username string - noWatermark bool - exclude string -} func main() { - config := parseOptions() - checkPath(config.path) - wconfig := web.Init() - go processWebChanges(wconfig) + parseOptions() + checkPath(config.Config.Path) - log.Print("Opening web browser") - open.Start("http://localhost:9090") + // log.Print("Opening web browser") + // open.Start("http://localhost:9090") + go web.StartWebServer() checkUpdates() - log.Print("Waiting for images to appear in ", config.path) + log.Print("Waiting for images to appear in ", config.Config.Path) // wander the path, forever for { - err := filepath.Walk(config.path, - func(path string, f os.FileInfo, err error) error { return checkFile(path, f, err, config) }) + err := filepath.Walk(config.Config.Path, + func(path string, f os.FileInfo, err error) error { return checkFile(path, f, err) }) if err != nil { log.Fatal("could not watch path", err) } lastCheck = newLastCheck log.Print("sleeping before next check"); - time.Sleep(time.Duration(config.watch) * time.Second) - } -} - -func processWebChanges(wc web.DAUWebServer) { - for { - change := <-wc.ConfigChange - log.Print(change) - log.Print("Got a change!") + time.Sleep(time.Duration(config.Config.Watch) * time.Second) } } @@ -118,8 +99,8 @@ func checkUpdates() { log.Fatal("could not parse JSON: ", err) } - if currentVersion < latest.TagName { - fmt.Printf("You are currently on version %s, but version %s is available\n", currentVersion, latest.TagName) + if config.CurrentVersion < latest.TagName { + fmt.Printf("You are currently on version %s, but version %s is available\n", config.CurrentVersion, latest.TagName) fmt.Println("----------- Release Info -----------") fmt.Println(latest.Body) fmt.Println("------------------------------------") @@ -128,9 +109,8 @@ func checkUpdates() { } -func parseOptions() Config { +func parseOptions() { - var newConfig Config // Declare the flags to be used webhookFlag := getopt.StringLong("webhook", 'w', "", "discord webhook URL") pathFlag := getopt.StringLong("directory", 'd', "", "directory to scan, optional, defaults to current directory") @@ -151,7 +131,7 @@ func parseOptions() Config { if *versionFlag { fmt.Println("dau - https://github.com/tardisx/discord-auto-upload") - fmt.Printf("Version: %s\n", currentVersion) + fmt.Printf("Version: %s\n", config.CurrentVersion) os.Exit(0) } @@ -164,23 +144,22 @@ func parseOptions() Config { log.Fatal("ERROR: You must specify a --webhook URL") } - newConfig.path = *pathFlag - newConfig.webhookURL = *webhookFlag - newConfig.watch = int(*watchFlag) - newConfig.username = *usernameFlag - newConfig.noWatermark = *noWatermarkFlag - newConfig.exclude = *excludeFlag + config.Config.Path = *pathFlag + config.Config.WebHookURL = *webhookFlag + config.Config.Watch = int(*watchFlag) + config.Config.Username = *usernameFlag + config.Config.NoWatermark = *noWatermarkFlag + config.Config.Exclude = *excludeFlag - return newConfig } -func checkFile(path string, f os.FileInfo, err error, config Config) error { +func checkFile(path string, f os.FileInfo, err error) error { if f.ModTime().After(lastCheck) && f.Mode().IsRegular() { - if fileEligible(config, path) { + if fileEligible(path) { // process file - processFile(config, path) + processFile(path) } if newLastCheck.Before(f.ModTime()) { @@ -191,9 +170,9 @@ func checkFile(path string, f os.FileInfo, err error, config Config) error { return nil } -func fileEligible(config Config, file string) bool { +func fileEligible(file string) bool { - if config.exclude != "" && strings.Contains(file, config.exclude) { + if config.Config.Exclude != "" && strings.Contains(file, config.Config.Exclude) { return false } @@ -205,9 +184,9 @@ func fileEligible(config Config, file string) bool { return false } -func processFile(config Config, file string) { +func processFile(file string) { - if !config.noWatermark { + if !config.Config.NoWatermark { log.Print("Copying to temp location and watermarking ", file) file = mungeFile(file) } @@ -216,8 +195,9 @@ func processFile(config Config, file string) { extraParams := map[string]string{} - if config.username != "" { - extraParams["username"] = config.username + if config.Config.Username != "" { + log.Print("Overriding username with " + config.Config.Username) + extraParams["username"] = config.Config.Username } type DiscordAPIResponseAttachment struct { @@ -237,7 +217,7 @@ func processFile(config Config, file string) { var retriesRemaining = 5 for retriesRemaining > 0 { - request, err := newfileUploadRequest(config.webhookURL, extraParams, "file", file) + request, err := newfileUploadRequest(config.Config.WebHookURL, extraParams, "file", file) if err != nil { log.Fatal(err) } @@ -296,7 +276,7 @@ func processFile(config Config, file string) { } } - if !config.noWatermark { + if !config.Config.NoWatermark { log.Print("Removing temporary file ", file) os.Remove(file) } diff --git a/web/server.go b/web/server.go index 65ae2d7..fb8d147 100644 --- a/web/server.go +++ b/web/server.go @@ -8,6 +8,12 @@ import ( "github.com/tardisx/discord-auto-upload/assets" // "strings" "regexp" + "github.com/tardisx/discord-auto-upload/config" + "os" + "path/filepath" + "mime" + "text/template" + ) // DAUWebServer - stuff for the web server @@ -15,66 +21,124 @@ type DAUWebServer struct { ConfigChange chan int } -type response struct { - Success int +type valueStringResponse struct { + Success bool `json: 'success'` + Value string `json: 'value'` } -// I am too noob to work out how to pass context around -var wsConfig DAUWebServer - -// r.ParseForm() // parse arguments, you have to call this by yourself -// fmt.Println(r.Form) // print form information in server side -// fmt.Println("path", r.URL.Path) -// fmt.Println("scheme", r.URL.Scheme) -// fmt.Println(r.Form["url_long"]) -// for k, v := range r.Form { -// fmt.Println("key:", k) -// fmt.Println("val:", strings.Join(v, "")) -// } +type errorResponse struct { + Success bool `json: 'success'` + Error string `json: 'error'` +} func getStatic(w http.ResponseWriter, r *http.Request) { // haha this is dumb and I should change it - fmt.Println(r.URL) + // fmt.Println(r.URL) re := regexp.MustCompile(`[^a-zA-Z0-9\.]`) - path := r.URL.Path + path := r.URL.Path[1:] sanitized_path := re.ReplaceAll([]byte(path), []byte("_")) - fmt.Println(sanitized_path) + if string(sanitized_path) == "" { + sanitized_path = []byte("index.html"); + } data, err := assets.Asset(string(sanitized_path)) if err != nil { // Asset was not found. fmt.Fprintln(w, err) } + + + extension := filepath.Ext(string(sanitized_path)) + + // is this a HTML file? if so wrap it in the template + if extension == ".html" { + wrapper, _ := assets.Asset("wrapper.tmpl") + t := template.Must(template.New("wrapper").Parse(string(wrapper))) + var b struct { + Body string + Path string + Version string + } + b.Body = string(data) + b.Path = string(sanitized_path) + b.Version = config.CurrentVersion + t.Execute(w, b) + return + } + + // otherwise we are a static thing + w.Header().Set("Content-Type", mime.TypeByExtension(extension)) + w.Write(data) // } +// TODO there should be locks around all these config accesses func getSetWebhook(w http.ResponseWriter, r *http.Request) { - wsConfig.ConfigChange <- 1 - goodResponse := response{1} - js, err := json.Marshal(goodResponse) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) + if r.Method == "GET" { + getResponse := valueStringResponse{ Success: true, Value: config.Config.WebHookURL } + + // I can't see any way this will fail + js, _ := json.Marshal(getResponse) + w.Write(js) + } else if r.Method == "POST" { + err := r.ParseForm() + if err != nil { + log.Fatal(err) + } + config.Config.WebHookURL = r.PostForm.Get("value") + postResponse := valueStringResponse{ Success: true, Value: config.Config.WebHookURL } + + js, _ := json.Marshal(postResponse) + w.Write(js) + } } func getSetDirectory(w http.ResponseWriter, r *http.Request) { + log.Print("ok") + w.Header().Set("Content-Type", "application/json") + + if r.Method == "GET" { + getResponse := valueStringResponse{ Success: true, Value: config.Config.Path } + + // I can't see any way this will fail + js, _ := json.Marshal(getResponse) + w.Write(js) + } else if r.Method == "POST" { + err := r.ParseForm() + if err != nil { + log.Fatal(err) + } + newPath := r.PostForm.Get("value") + + // sanity check this path + stat, err := os.Stat(newPath) + if os.IsNotExist(err) { + // not exist + response := errorResponse{ Success: false, Error: fmt.Sprintf("Path: %s - does not exist", newPath) } + js, _ := json.Marshal(response) + w.Write(js) + return; + } else if ! stat.IsDir() { + // not a directory + response := errorResponse{ Success: false, Error: fmt.Sprintf("Path: %s - is not a directory", newPath) } + js, _ := json.Marshal(response) + w.Write(js) + return; + } + + config.Config.Path = newPath + postResponse := valueStringResponse{ Success: true, Value: config.Config.Path } + + js, _ := json.Marshal(postResponse) + w.Write(js) + } } -// Init is great -func Init() DAUWebServer { - wsConfig.ConfigChange = make(chan int) - go startWebServer() - return wsConfig -} - -func startWebServer() { +func StartWebServer() { http.HandleFunc("/", getStatic) http.HandleFunc("/rest/config/webhook", getSetWebhook) http.HandleFunc("/rest/config/directory", getSetDirectory)