Rework configuration to it's own package and make it available to web server. Start a template driven web interface.

This commit is contained in:
Justin Hawkins 2021-01-31 17:53:32 +10:30
parent 55bb5a8bae
commit 46a0f5a187
8 changed files with 326 additions and 90 deletions

View File

@ -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;

13
config/config.go Normal file
View File

@ -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"

8
data/config.html Normal file
View File

@ -0,0 +1,8 @@
<main role="main" class="inner DAU">
<h1 class="DAU-heading">Config</h1>
<p class="lead">Hey look it's DAU.</p>
<p class="lead">
<a href="#" class="btn btn-lg btn-secondary">Learn more</a>
</p>
</main>

106
data/dau.css Normal file
View File

@ -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);
}

View File

@ -1 +1,8 @@
THIS IS SPARTA
<main role="main" class="inner DAU">
<h1 class="DAU-heading">Discord Auto Upload</h1>
<p class="lead">Hey look it's DAU.</p>
<p class="lead">
<a href="#" class="btn btn-lg btn-secondary">Learn more</a>
</p>
</main>

58
data/wrapper.tmpl Normal file
View File

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
<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;
}
}
</style>
<!-- Custom styles for this template -->
<link href="/dau.css" rel="stylesheet">
</head>
<body class="text-center">
<div class="DAU-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">discord-auto-upload ({{.Version}})</h3>
<nav class="nav nav-masthead justify-content-center">
<a class="nav-link {{ if eq .Path "index.html"}} active {{ end }}" href="/">Home</a>
<a class="nav-link {{ if eq .Path "config.html"}} active {{ end }}" href="/config.html">Config</a>
</nav>
</div>
</header>
{{.Body}}
<footer class="mastfoot mt-auto">
<div class="inner">
<!-- <p>DAU template for <a href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p> -->
</div>
</footer>
</div>
</body>
</html>

86
dau.go
View File

@ -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)
}

View File

@ -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)