Big refactor to allow for multiple watchers, v2 configuration file with migration and new UI for configuration
This commit is contained in:
parent
7dddc92364
commit
8483fe7db9
106
config/config.go
106
config/config.go
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
daulog "github.com/tardisx/discord-auto-upload/log"
|
daulog "github.com/tardisx/discord-auto-upload/log"
|
||||||
|
|
||||||
@ -21,62 +22,115 @@ type ConfigV1 struct {
|
|||||||
Exclude string
|
Exclude string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigV2Watcher struct {
|
type Watcher struct {
|
||||||
WebHookURL string
|
WebHookURL string
|
||||||
Path string
|
Path string
|
||||||
Username string
|
Username string
|
||||||
NoWatermark bool
|
NoWatermark bool
|
||||||
Exclude string
|
Exclude []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigV2 struct {
|
type ConfigV2 struct {
|
||||||
WatchInterval int
|
WatchInterval int
|
||||||
Version int
|
Version int
|
||||||
Watchers []ConfigV2Watcher
|
Port int
|
||||||
|
Watchers []Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
var Config ConfigV2
|
type ConfigService struct {
|
||||||
var configPath string
|
Config ConfigV2
|
||||||
|
ConfigFilename string
|
||||||
|
}
|
||||||
|
|
||||||
func Init() {
|
func DefaultConfigService() *ConfigService {
|
||||||
configPath = defaultConfigPath()
|
c := ConfigService{
|
||||||
|
ConfigFilename: defaultConfigPath(),
|
||||||
|
}
|
||||||
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadOrInit loads the current configuration from the config file, or creates
|
// LoadOrInit loads the current configuration from the config file, or creates
|
||||||
// a new config file if none exists.
|
// a new config file if none exists.
|
||||||
func LoadOrInit() error {
|
func (c *ConfigService) LoadOrInit() error {
|
||||||
daulog.SendLog(fmt.Sprintf("Trying to load config from %s", configPath), daulog.LogTypeDebug)
|
daulog.SendLog(fmt.Sprintf("Trying to load config from %s\n", c.ConfigFilename), daulog.LogTypeDebug)
|
||||||
_, err := os.Stat(configPath)
|
_, err := os.Stat(c.ConfigFilename)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
daulog.SendLog("NOTE: No config file, writing out sample configuration", daulog.LogTypeInfo)
|
daulog.SendLog("NOTE: No config file, writing out sample configuration", daulog.LogTypeInfo)
|
||||||
daulog.SendLog("You need to set the configuration via the web interface", daulog.LogTypeInfo)
|
daulog.SendLog("You need to set the configuration via the web interface", daulog.LogTypeInfo)
|
||||||
Config.Version = 2
|
c.Config = *DefaultConfig()
|
||||||
Config.WatchInterval = 10
|
return c.Save()
|
||||||
return SaveConfig()
|
|
||||||
} else {
|
} else {
|
||||||
return LoadConfig()
|
return c.Load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig will load the configuration from a known-to-exist config file.
|
func DefaultConfig() *ConfigV2 {
|
||||||
func LoadConfig() error {
|
c := ConfigV2{}
|
||||||
data, err := ioutil.ReadFile(configPath)
|
c.Version = 2
|
||||||
if err != nil {
|
c.WatchInterval = 10
|
||||||
return fmt.Errorf("cannot read config file %s: %s", configPath, err.Error())
|
c.Port = 9090
|
||||||
|
w := Watcher{
|
||||||
|
WebHookURL: "abcedf",
|
||||||
|
Path: "/Users/justin/tmp",
|
||||||
|
Username: "",
|
||||||
|
NoWatermark: false,
|
||||||
|
Exclude: []string{},
|
||||||
}
|
}
|
||||||
err = json.Unmarshal([]byte(data), &Config)
|
c.Watchers = []Watcher{w}
|
||||||
if err != nil {
|
return &c
|
||||||
return fmt.Errorf("cannot decode config file %s: %s", configPath, err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load will load the configuration from a known-to-exist config file.
|
||||||
|
func (c *ConfigService) Load() error {
|
||||||
|
fmt.Printf("Loading from %s\n\n", c.ConfigFilename)
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(c.ConfigFilename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot read config file %s: %s", c.ConfigFilename, err.Error())
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(data), &c.Config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode config file %s: %s", c.ConfigFilename, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Got config: %#v", c.Config)
|
||||||
|
|
||||||
|
// Version 0 predates config migrations
|
||||||
|
if c.Config.Version == 0 {
|
||||||
|
// need to migrate this
|
||||||
|
daulog.SendLog("Migrating config to V2", daulog.LogTypeInfo)
|
||||||
|
|
||||||
|
configV1 := ConfigV1{}
|
||||||
|
err = json.Unmarshal([]byte(data), &configV1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode legacy config file as v1 %s: %s", c.ConfigFilename, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy stuff across
|
||||||
|
c.Config.Version = 2
|
||||||
|
c.Config.WatchInterval = configV1.Watch
|
||||||
|
c.Config.Port = 9090 // this never used to be configurable
|
||||||
|
|
||||||
|
onlyWatcher := Watcher{
|
||||||
|
WebHookURL: configV1.WebHookURL,
|
||||||
|
Path: configV1.Path,
|
||||||
|
Username: configV1.Username,
|
||||||
|
NoWatermark: configV1.NoWatermark,
|
||||||
|
Exclude: strings.Split(configV1.Exclude, " "),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Config.Watchers = []Watcher{onlyWatcher}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveConfig() error {
|
func (c *ConfigService) Save() error {
|
||||||
daulog.SendLog("saving configuration", daulog.LogTypeInfo)
|
daulog.SendLog("saving configuration", daulog.LogTypeInfo)
|
||||||
jsonString, _ := json.Marshal(Config)
|
jsonString, _ := json.Marshal(c.Config)
|
||||||
err := ioutil.WriteFile(configPath, jsonString, os.ModePerm)
|
err := ioutil.WriteFile(c.ConfigFilename, jsonString, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot save config %s: %s", configPath, err.Error())
|
return fmt.Errorf("cannot save config %s: %s", c.ConfigFilename, err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -7,39 +7,76 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNoConfig(t *testing.T) {
|
func TestNoConfig(t *testing.T) {
|
||||||
if Config.Version != 0 {
|
c := ConfigService{}
|
||||||
t.Error("not 0 empty config")
|
|
||||||
}
|
|
||||||
|
|
||||||
configPath = emptyTempFile()
|
c.ConfigFilename = emptyTempFile()
|
||||||
os.Remove(configPath)
|
os.Remove(c.ConfigFilename)
|
||||||
|
defer os.Remove(c.ConfigFilename) // because we are about to create it
|
||||||
|
|
||||||
err := LoadOrInit()
|
err := c.LoadOrInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected failure from load: %s", err)
|
t.Errorf("unexpected failure from load: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Config.Version != 2 {
|
if c.Config.Version != 2 {
|
||||||
t.Error("not version 2 starting config")
|
t.Error("not version 2 starting config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileSize(configPath) < 40 {
|
if fileSize(c.ConfigFilename) < 40 {
|
||||||
t.Errorf("File is too small %d bytes", fileSize(configPath))
|
t.Errorf("File is too small %d bytes", fileSize(c.ConfigFilename))
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove(configPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyFileConfig(t *testing.T) {
|
func TestEmptyFileConfig(t *testing.T) {
|
||||||
|
c := ConfigService{}
|
||||||
|
|
||||||
configPath = emptyTempFile()
|
c.ConfigFilename = emptyTempFile()
|
||||||
|
defer os.Remove(c.ConfigFilename)
|
||||||
|
|
||||||
err := LoadOrInit()
|
err := c.LoadOrInit()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("unexpected success from LoadOrInit()")
|
t.Error("unexpected success from LoadOrInit()")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Remove(configPath)
|
}
|
||||||
|
|
||||||
|
func TestMigrateFromV1toV2(t *testing.T) {
|
||||||
|
c := ConfigService{}
|
||||||
|
|
||||||
|
c.ConfigFilename = v1Config()
|
||||||
|
err := c.LoadOrInit()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error from LoadOrInit()")
|
||||||
|
}
|
||||||
|
if c.Config.Version != 2 {
|
||||||
|
t.Errorf("Version %d not 2", c.Config.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Config.Watchers) != 1 {
|
||||||
|
t.Error("wrong amount of watchers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config.Watchers[0].Path != "/private/tmp" {
|
||||||
|
t.Error("Wrong path")
|
||||||
|
}
|
||||||
|
if c.Config.WatchInterval != 69 {
|
||||||
|
t.Error("Wrong watch interval")
|
||||||
|
}
|
||||||
|
if c.Config.Port != 9090 {
|
||||||
|
t.Error("Wrong port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func v1Config() string {
|
||||||
|
f, err := ioutil.TempFile("", "dautest-*")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
config := `{"WebHookURL":"https://discord.com/api/webhooks/abc123","Path":"/private/tmp","Watch":69,"Username":"abcdedf","NoWatermark":true,"Exclude":"ab cd ef"}`
|
||||||
|
f.Write([]byte(config))
|
||||||
|
defer f.Close()
|
||||||
|
return f.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func emptyTempFile() string {
|
func emptyTempFile() string {
|
||||||
|
168
dau.go
168
dau.go
@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -15,59 +17,132 @@ import (
|
|||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
|
||||||
"github.com/pborman/getopt"
|
|
||||||
|
|
||||||
// "github.com/skratchdot/open-golang/open"
|
// "github.com/skratchdot/open-golang/open"
|
||||||
|
|
||||||
"github.com/tardisx/discord-auto-upload/config"
|
"github.com/tardisx/discord-auto-upload/config"
|
||||||
daulog "github.com/tardisx/discord-auto-upload/log"
|
daulog "github.com/tardisx/discord-auto-upload/log"
|
||||||
"github.com/tardisx/discord-auto-upload/uploads"
|
"github.com/tardisx/discord-auto-upload/upload"
|
||||||
|
|
||||||
|
// "github.com/tardisx/discord-auto-upload/upload"
|
||||||
"github.com/tardisx/discord-auto-upload/version"
|
"github.com/tardisx/discord-auto-upload/version"
|
||||||
"github.com/tardisx/discord-auto-upload/web"
|
"github.com/tardisx/discord-auto-upload/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastCheck = time.Now()
|
type watch struct {
|
||||||
var newLastCheck = time.Now()
|
lastCheck time.Time
|
||||||
|
newLastCheck time.Time
|
||||||
|
config config.Watcher
|
||||||
|
uploader upload.Uploader
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
parseOptions()
|
parseOptions()
|
||||||
|
|
||||||
|
// grab the config
|
||||||
|
config := config.DefaultConfigService()
|
||||||
|
config.LoadOrInit()
|
||||||
|
|
||||||
|
// create the uploader
|
||||||
|
up := upload.Uploader{}
|
||||||
|
|
||||||
// log.Print("Opening web browser")
|
// log.Print("Opening web browser")
|
||||||
// open.Start("http://localhost:9090")
|
// open.Start("http://localhost:9090")
|
||||||
|
web := web.WebService{Config: *config}
|
||||||
web.StartWebServer()
|
web.StartWebServer()
|
||||||
|
|
||||||
checkUpdates()
|
go func() { checkUpdates() }()
|
||||||
|
|
||||||
daulog.SendLog(fmt.Sprintf("Waiting for images to appear in %s", config.Config.Path), daulog.LogTypeInfo)
|
// create the watchers
|
||||||
// wander the path, forever
|
|
||||||
|
log.Printf("Conf: %#v", config.Config)
|
||||||
|
|
||||||
|
for _, c := range config.Config.Watchers {
|
||||||
|
log.Printf("Creating watcher for %v", c)
|
||||||
|
watcher := watch{uploader: up, lastCheck: time.Now(), newLastCheck: time.Now(), config: c}
|
||||||
|
go watcher.Watch(config.Config.WatchInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watch) Watch(interval int) {
|
||||||
for {
|
for {
|
||||||
if checkPath(config.Config.Path) {
|
newFiles := w.ProcessNewFiles()
|
||||||
err := filepath.Walk(config.Config.Path,
|
for _, f := range newFiles {
|
||||||
func(path string, f os.FileInfo, err error) error { return checkFile(path, f, err) })
|
w.uploader.AddFile(f, w.config)
|
||||||
|
}
|
||||||
|
// upload them
|
||||||
|
w.uploader.Upload()
|
||||||
|
daulog.SendLog(fmt.Sprintf("sleeping for %ds before next check of %s", interval, w.config.Path), daulog.LogTypeDebug)
|
||||||
|
time.Sleep(time.Duration(interval) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessNewFiles returns an array of new files that have appeared since
|
||||||
|
// the last time ProcessNewFiles was run.
|
||||||
|
func (w *watch) ProcessNewFiles() []string {
|
||||||
|
var newFiles []string
|
||||||
|
// check the path each time around, in case it goes away or something
|
||||||
|
if w.checkPath() {
|
||||||
|
// walk the path
|
||||||
|
err := filepath.WalkDir(w.config.Path,
|
||||||
|
func(path string, d fs.DirEntry, err error) error {
|
||||||
|
return w.checkFile(path, &newFiles)
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not watch path", err)
|
log.Fatal("could not watch path", err)
|
||||||
}
|
}
|
||||||
lastCheck = newLastCheck
|
w.lastCheck = w.newLastCheck
|
||||||
}
|
|
||||||
daulog.SendLog(fmt.Sprintf("sleeping for %ds before next check of %s", config.Config.Watch, config.Config.Path), daulog.LogTypeDebug)
|
|
||||||
time.Sleep(time.Duration(config.Config.Watch) * time.Second)
|
|
||||||
}
|
}
|
||||||
|
return newFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPath(path string) bool {
|
// checkPath makes sure the path exists, and is a directory.
|
||||||
src, err := os.Stat(path)
|
// It logs errors if there are problems, and returns false
|
||||||
|
func (w *watch) checkPath() bool {
|
||||||
|
src, err := os.Stat(w.config.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Problem with path '%s': %s", path, err)
|
log.Printf("Problem with path '%s': %s", w.config.Path, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !src.IsDir() {
|
if !src.IsDir() {
|
||||||
log.Printf("Problem with path '%s': is not a directory", path)
|
log.Printf("Problem with path '%s': is not a directory", w.config.Path)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkFile checks if a file is eligible, first looking at extension (to
|
||||||
|
// avoid statting files uselessly) then modification times.
|
||||||
|
// If the file is eligble, and new enough to care we add it to the passed in
|
||||||
|
// array of files
|
||||||
|
func (w *watch) checkFile(path string, found *[]string) error {
|
||||||
|
log.Printf("Considering %s", path)
|
||||||
|
|
||||||
|
extension := strings.ToLower(filepath.Ext(path))
|
||||||
|
|
||||||
|
if !(extension == ".png" || extension == ".jpg" || extension == ".gif") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.ModTime().After(w.lastCheck) && fi.Mode().IsRegular() {
|
||||||
|
*found = append(*found, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.newLastCheck.Before(fi.ModTime()) {
|
||||||
|
w.newLastCheck = fi.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func checkUpdates() {
|
func checkUpdates() {
|
||||||
|
|
||||||
type GithubRelease struct {
|
type GithubRelease struct {
|
||||||
@ -115,61 +190,14 @@ func checkUpdates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseOptions() {
|
func parseOptions() {
|
||||||
|
var versionFlag bool
|
||||||
|
flag.BoolVar(&versionFlag, "version", false, "show version")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
// Declare the flags to be used
|
if versionFlag {
|
||||||
helpFlag := getopt.BoolLong("help", 'h', "help")
|
|
||||||
versionFlag := getopt.BoolLong("version", 'v', "show version")
|
|
||||||
getopt.SetParameters("")
|
|
||||||
|
|
||||||
getopt.Parse()
|
|
||||||
|
|
||||||
if *helpFlag {
|
|
||||||
getopt.PrintUsage(os.Stderr)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *versionFlag {
|
|
||||||
fmt.Println("dau - https://github.com/tardisx/discord-auto-upload")
|
fmt.Println("dau - https://github.com/tardisx/discord-auto-upload")
|
||||||
fmt.Printf("Version: %s\n", version.CurrentVersion)
|
fmt.Printf("Version: %s\n", version.CurrentVersion)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the config
|
|
||||||
config.LoadOrInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkFile(path string, f os.FileInfo, err error) error {
|
|
||||||
if f.ModTime().After(lastCheck) && f.Mode().IsRegular() {
|
|
||||||
|
|
||||||
if fileEligible(path) {
|
|
||||||
// process file
|
|
||||||
processFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newLastCheck.Before(f.ModTime()) {
|
|
||||||
newLastCheck = f.ModTime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileEligible(file string) bool {
|
|
||||||
|
|
||||||
if config.Config.Exclude != "" && strings.Contains(file, config.Config.Exclude) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
extension := strings.ToLower(filepath.Ext(file))
|
|
||||||
if extension == ".png" || extension == ".jpg" || extension == ".gif" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFile(file string) {
|
|
||||||
|
|
||||||
daulog.SendLog("Sending to uploader", daulog.LogTypeInfo)
|
|
||||||
uploads.AddFile(file)
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,4 +43,5 @@ func SendLog(entry string, entryType LogEntryType) {
|
|||||||
Entry: entry,
|
Entry: entry,
|
||||||
Type: entryType,
|
Type: entryType,
|
||||||
}
|
}
|
||||||
|
log.Printf(entry)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// The uploads pacakge encapsulates dealing with file uploads to
|
// Package upload encapsulates prepping an image for sending to discord,
|
||||||
// discord
|
// and actually uploading it there.
|
||||||
package uploads
|
package upload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -22,48 +22,70 @@ import (
|
|||||||
"golang.org/x/image/font/inconsolata"
|
"golang.org/x/image/font/inconsolata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Uploader struct {
|
||||||
|
Uploads []*Upload
|
||||||
|
}
|
||||||
|
|
||||||
type Upload struct {
|
type Upload struct {
|
||||||
Uploaded bool `json:"uploaded"` // has this file been uploaded to discord
|
Uploaded bool `json:"uploaded"` // has this file been uploaded to discord
|
||||||
UploadedAt time.Time `json:"uploaded_at"`
|
UploadedAt time.Time `json:"uploaded_at"`
|
||||||
|
|
||||||
originalFilename string // path on the local disk
|
originalFilename string // path on the local disk
|
||||||
mungedFilename string // post-watermark
|
filenameToUpload string // post-watermark, or just original if unwatermarked
|
||||||
|
|
||||||
|
webhookURL string
|
||||||
|
|
||||||
|
watermark bool // should watermark
|
||||||
|
|
||||||
|
usernameOverride string
|
||||||
|
|
||||||
Url string `json:"url"` // url on the discord CDN
|
Url string `json:"url"` // url on the discord CDN
|
||||||
|
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Uploads []*Upload
|
func (u *Uploader) AddFile(file string, conf config.Watcher) {
|
||||||
|
|
||||||
func AddFile(file string) {
|
|
||||||
thisUpload := Upload{
|
thisUpload := Upload{
|
||||||
Uploaded: false,
|
Uploaded: false,
|
||||||
originalFilename: file,
|
originalFilename: file,
|
||||||
|
watermark: !conf.NoWatermark,
|
||||||
|
webhookURL: conf.WebHookURL,
|
||||||
|
usernameOverride: conf.Username,
|
||||||
}
|
}
|
||||||
Uploads = append(Uploads, &thisUpload)
|
u.Uploads = append(u.Uploads, &thisUpload)
|
||||||
|
|
||||||
ProcessUpload(&thisUpload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessUpload(up *Upload) {
|
// Upload uploads any files that have not yet been uploaded
|
||||||
|
func (u *Uploader) Upload() {
|
||||||
file := up.originalFilename
|
for _, upload := range u.Uploads {
|
||||||
|
if !upload.Uploaded {
|
||||||
if !config.Config.NoWatermark {
|
upload.processUpload()
|
||||||
daulog.SendLog("Copying to temp location and watermarking ", daulog.LogTypeInfo)
|
}
|
||||||
file = mungeFile(file)
|
}
|
||||||
up.mungedFilename = file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Config.WebHookURL == "" {
|
func (u *Upload) processUpload() {
|
||||||
|
|
||||||
|
// file := u.originalFilename
|
||||||
|
|
||||||
|
if u.webhookURL == "" {
|
||||||
daulog.SendLog("WebHookURL is not configured - cannot upload!", daulog.LogTypeError)
|
daulog.SendLog("WebHookURL is not configured - cannot upload!", daulog.LogTypeError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.watermark {
|
||||||
|
daulog.SendLog("Watermarking", daulog.LogTypeInfo)
|
||||||
|
u.applyWatermark()
|
||||||
|
} else {
|
||||||
|
u.filenameToUpload = u.originalFilename
|
||||||
|
}
|
||||||
|
|
||||||
extraParams := map[string]string{}
|
extraParams := map[string]string{}
|
||||||
|
|
||||||
if config.Config.Username != "" {
|
if u.usernameOverride != "" {
|
||||||
daulog.SendLog("Overriding username with "+config.Config.Username, daulog.LogTypeInfo)
|
daulog.SendLog("Overriding username with "+u.usernameOverride, daulog.LogTypeInfo)
|
||||||
extraParams["username"] = config.Config.Username
|
extraParams["username"] = u.usernameOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiscordAPIResponseAttachment struct {
|
type DiscordAPIResponseAttachment struct {
|
||||||
@ -83,7 +105,7 @@ func ProcessUpload(up *Upload) {
|
|||||||
var retriesRemaining = 5
|
var retriesRemaining = 5
|
||||||
for retriesRemaining > 0 {
|
for retriesRemaining > 0 {
|
||||||
|
|
||||||
request, err := newfileUploadRequest(config.Config.WebHookURL, extraParams, "file", file)
|
request, err := newfileUploadRequest(u.webhookURL, extraParams, "file", u.filenameToUpload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -145,19 +167,19 @@ func ProcessUpload(up *Upload) {
|
|||||||
daulog.SendLog(fmt.Sprintf("Uploaded to %s %dx%d", a.URL, a.Width, a.Height), daulog.LogTypeInfo)
|
daulog.SendLog(fmt.Sprintf("Uploaded to %s %dx%d", a.URL, a.Width, a.Height), daulog.LogTypeInfo)
|
||||||
daulog.SendLog(fmt.Sprintf("id: %d, %d bytes transferred in %.2f seconds (%.2f KiB/s)", res.ID, a.Size, elapsed.Seconds(), rate), daulog.LogTypeInfo)
|
daulog.SendLog(fmt.Sprintf("id: %d, %d bytes transferred in %.2f seconds (%.2f KiB/s)", res.ID, a.Size, elapsed.Seconds(), rate), daulog.LogTypeInfo)
|
||||||
|
|
||||||
up.Url = a.URL
|
u.Url = a.URL
|
||||||
up.Uploaded = true
|
u.Uploaded = true
|
||||||
up.Width = a.Width
|
u.Width = a.Width
|
||||||
up.Height = a.Height
|
u.Height = a.Height
|
||||||
up.UploadedAt = time.Now()
|
u.UploadedAt = time.Now()
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.Config.NoWatermark {
|
if u.watermark {
|
||||||
daulog.SendLog(fmt.Sprintf("Removing temporary file: %s", file), daulog.LogTypeDebug)
|
daulog.SendLog(fmt.Sprintf("Removing temporary file: %s", u.filenameToUpload), daulog.LogTypeDebug)
|
||||||
os.Remove(file)
|
os.Remove(u.filenameToUpload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if retriesRemaining == 0 {
|
if retriesRemaining == 0 {
|
||||||
@ -196,9 +218,9 @@ func newfileUploadRequest(uri string, params map[string]string, paramName, path
|
|||||||
return req, err
|
return req, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func mungeFile(path string) string {
|
func (u *Upload) applyWatermark() {
|
||||||
|
|
||||||
reader, err := os.Open(path)
|
reader, err := os.Open(u.originalFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -206,7 +228,10 @@ func mungeFile(path string) string {
|
|||||||
|
|
||||||
im, _, err := image.Decode(reader)
|
im, _, err := image.Decode(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Printf("Cannot decode image: %v - skipping watermarking", err)
|
||||||
|
u.watermark = false
|
||||||
|
u.filenameToUpload = u.originalFilename
|
||||||
|
return
|
||||||
}
|
}
|
||||||
bounds := im.Bounds()
|
bounds := im.Bounds()
|
||||||
// var S float64 = float64(bounds.Max.X)
|
// var S float64 = float64(bounds.Max.X)
|
||||||
@ -236,7 +261,7 @@ func mungeFile(path string) string {
|
|||||||
actualName := tempfile.Name() + ".png"
|
actualName := tempfile.Name() + ".png"
|
||||||
|
|
||||||
dc.SavePNG(actualName)
|
dc.SavePNG(actualName)
|
||||||
return actualName
|
u.filenameToUpload = actualName
|
||||||
}
|
}
|
||||||
|
|
||||||
func sleepForRetries(retry int) {
|
func sleepForRetries(retry int) {
|
@ -1,159 +1,153 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
|
||||||
<main role="main" class="inner DAU">
|
<main role="main" class="inner DAU" x-data="configuration()" x-init="get_config()">
|
||||||
<h1 class="DAU-heading">Config</h1>
|
<h1 class="DAU-heading">Config</h1>
|
||||||
<p class="lead">Discord-auto-upload configuration</p>
|
|
||||||
<a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks"><p class="lead">How to find your discord webhook</p></a>
|
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
<form class="">
|
<form class="">
|
||||||
<div class="form-row align-items-center config-item" data-key="webhook">
|
|
||||||
<div class="col-sm-5 my-1">
|
|
||||||
<span>Discord WebHook URL</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 my-1">
|
|
||||||
<label class="sr-only" for="inlineFormInputName">Name</label>
|
|
||||||
<input type="text" class="form-control rest-field" placeholder="https://....">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="">
|
<p>Configuration changes are not made until the Save button is pressed
|
||||||
<div class="form-row align-items-center config-item" data-key="username">
|
at the bottom of this page.
|
||||||
<div class="col-sm-5 my-1">
|
</p>
|
||||||
<span>Bot username (optional)</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 my-1">
|
|
||||||
<label class="sr-only" for="inlineFormInputName">Name</label>
|
|
||||||
<input type="text" class="form-control rest-field" placeholder="">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="">
|
<h3>global configuration</h3>
|
||||||
<div class="form-row align-items-center config-item" data-key="directory">
|
|
||||||
<div class="col-sm-5 my-1">
|
<p>The server port dictates which TCP port the web server listens on.
|
||||||
|
If you change this number you will need to restart.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The Watch Interval is how often new files will be discovered by your
|
||||||
|
watchers (configured below).</p>
|
||||||
|
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<span>Server port</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<label class="sr-only">Server port</label>
|
||||||
|
<input type="text" class="form-control" placeholder="" x-model.number="config.Port">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<span>Watch interval</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<label class="sr-only">Watch interval</label>
|
||||||
|
<input type="text" class="form-control" placeholder="" x-model.number="config.WatchInterval">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>watcher configuration</h3>
|
||||||
|
|
||||||
|
<p>You may configure one or more watchers. Each watcher watches a
|
||||||
|
single directory (and all subdirectories) and when a new image file
|
||||||
|
is found it uploads it to the specified channel via the webhook URL.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks">
|
||||||
|
Click here</a> for information on how to find your discord webhook URL.</p>
|
||||||
|
|
||||||
|
<p>You may also specify a username for the bot to masquerade as. This is a cosmetic
|
||||||
|
change only, and does not hide the uploaders actual identity.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<template x-for="(watcher, i) in config.Watchers">
|
||||||
|
<div class="my-5">
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
<span>Directory to watch</span>
|
<span>Directory to watch</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 my-1">
|
<div class="col-sm-6 my-1">
|
||||||
<label class="sr-only" for="inlineFormInputName">Name</label>
|
<label class="sr-only" for="">Directory</label>
|
||||||
<input type="text" class="form-control rest-field" placeholder="/...">
|
<input type="text" class="form-control" placeholder="" x-model="watcher.Path">
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<span>Webhook URL</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<label class="sr-only" for="">WebHook URL</label>
|
||||||
|
<input type="text" class="form-control" placeholder="" x-model="watcher.WebHookURL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<span>Username</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<label class="sr-only" for="">Username</label>
|
||||||
|
<input type="text" class="form-control" placeholder="" x-model="watcher.Username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row align-items-center">
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<span>Watermark</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 my-1">
|
||||||
|
<button type="button" @click="config.Watchers[i].NoWatermark = ! config.Watchers[i].NoWatermark" class="btn btn-success" x-text="watcher.NoWatermark ? 'Disabled 😢' : 'Enabled'"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" href="#" @click.prevent="config.Watchers.splice(i, 1);">Remove
|
||||||
|
this watcher</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
<button type="button" class="btn btn-secondary" href="#"
|
||||||
|
@click.prevent="config.Watchers.push({Username: '', WebHookURL: '', Path: '', NoWatermark: false});">
|
||||||
|
Add a new watcher</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-5">
|
||||||
|
|
||||||
|
<button type="button" class="my-4 btn btn-danger" href="#" @click="save_config()">
|
||||||
|
Save all Configuration
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form class="">
|
|
||||||
<div class="form-row align-items-center config-item" data-key="watch">
|
|
||||||
<div class="col-sm-5 my-1">
|
|
||||||
<span>Period between filesystem checks (seconds)</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 my-1">
|
|
||||||
<label class="sr-only" for="inlineFormInputName">Seconds</label>
|
|
||||||
<input type="text" class="form-control rest-field " placeholder="/...">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="">
|
|
||||||
<div class="form-row align-items-center config-item" data-key="nowatermark">
|
|
||||||
<div class="col-sm-5 my-1">
|
|
||||||
<span>Do not watermark images</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 my-1">
|
|
||||||
<div class="custom-control custom-switch">
|
|
||||||
<input type="checkbox" class="custom-control-input rest-field rest-field-boolean" id="input-nowatermark">
|
|
||||||
<label class="custom-control-label" for="input-nowatermark"> </label>
|
|
||||||
<span id="sadness" style="">😭</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form class="">
|
|
||||||
<div class="form-row align-items-center config-item" data-key="exclude">
|
|
||||||
<div class="col-sm-5 my-1">
|
|
||||||
<span>Files to exclude</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 my-1">
|
|
||||||
<label class="sr-only" for="input-exclude">Name</label>
|
|
||||||
<input type="text" id="input-exclude" class="form-control rest-field" placeholder="">
|
|
||||||
</div>
|
|
||||||
<div class="col-auto my-1">
|
|
||||||
<button type="submit" class="btn btn-primary">update</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "js" }}
|
{{ define "js" }}
|
||||||
|
<script src="//unpkg.com/alpinejs" defer></script>
|
||||||
<script>
|
<script>
|
||||||
function update_sadness () {
|
function configuration() {
|
||||||
if ($('#input-nowatermark').prop('checked')) {
|
return {
|
||||||
$('#sadness').css('visibility','');
|
config: {},
|
||||||
|
get_config() {
|
||||||
|
fetch('/rest/config')
|
||||||
|
.then(response => response.json()) // convert to json
|
||||||
|
.then(json => {
|
||||||
|
this.config = json;
|
||||||
|
console.log(json);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
save_config() {
|
||||||
|
fetch('/rest/config', { method: 'POST', body: JSON.stringify(this.config) })
|
||||||
|
.then(response => response.json()) // convert to json
|
||||||
|
.then(json => {
|
||||||
|
this.config = json;
|
||||||
|
console.log(json);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
$('#sadness').css('visibility','hidden');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(document).ready(function() {
|
|
||||||
|
|
||||||
$('#input-nowatermark').on('click', function() { update_sadness(); });
|
|
||||||
|
|
||||||
// populate each field
|
|
||||||
$('.config-item').each(function() {
|
|
||||||
let el = $(this);
|
|
||||||
let key = el.data('key');
|
|
||||||
|
|
||||||
$.ajax({ method: 'get', url: '/rest/config/'+key})
|
|
||||||
.done(function(data) {
|
|
||||||
var this_el = $(".config-item[data-key='"+key+"']").find('.rest-field');
|
|
||||||
if (this_el.hasClass('rest-field-boolean')) {
|
|
||||||
this_el.prop('checked', data.value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
this_el.val(data.value);
|
|
||||||
}
|
|
||||||
update_sadness();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// respond to button clicks to update
|
|
||||||
$('.config-item button').on('click', function(e,f) {
|
|
||||||
key = $(this).parents('.config-item').data('key');
|
|
||||||
val = $(this).parents('.config-item').find('.rest-field').val();
|
|
||||||
if ($(this).parents('.config-item').find('.rest-field-boolean').length) {
|
|
||||||
val = $(this).parents('.config-item').find('.rest-field').prop('checked') ? 1 : 0;
|
|
||||||
}
|
|
||||||
$.post('/rest/config/'+key, { value: val })
|
|
||||||
.done(function(d) {
|
|
||||||
if (d.success) {
|
|
||||||
alert('Updated config');
|
|
||||||
} else {
|
|
||||||
alert("Error: " + d.error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
283
web/server.go
283
web/server.go
@ -5,44 +5,32 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tardisx/discord-auto-upload/config"
|
"github.com/tardisx/discord-auto-upload/config"
|
||||||
daulog "github.com/tardisx/discord-auto-upload/log"
|
daulog "github.com/tardisx/discord-auto-upload/log"
|
||||||
"github.com/tardisx/discord-auto-upload/uploads"
|
|
||||||
"github.com/tardisx/discord-auto-upload/version"
|
"github.com/tardisx/discord-auto-upload/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WebService struct {
|
||||||
|
Config config.ConfigService
|
||||||
|
}
|
||||||
|
|
||||||
//go:embed data
|
//go:embed data
|
||||||
var webFS embed.FS
|
var webFS embed.FS
|
||||||
|
|
||||||
// DAUWebServer - stuff for the web server
|
// DAUWebServer - stuff for the web server
|
||||||
type DAUWebServer struct {
|
type DAUWebServer struct {
|
||||||
ConfigChange chan int
|
// ConfigChange chan int
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type valueStringResponse struct {
|
func (ws *WebService) getStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
Success bool `json:"success"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type valueBooleanResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Value bool `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStatic(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
path = strings.TrimLeft(path, "/")
|
path = strings.TrimLeft(path, "/")
|
||||||
@ -52,8 +40,7 @@ func getStatic(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
extension := filepath.Ext(string(path))
|
extension := filepath.Ext(string(path))
|
||||||
|
|
||||||
if extension == ".html" {
|
if extension == ".html" { // html file
|
||||||
|
|
||||||
t, err := template.ParseFS(webFS, "data/wrapper.tmpl", "data/"+path)
|
t, err := template.ParseFS(webFS, "data/wrapper.tmpl", "data/"+path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("when fetching: %s got: %s", path, err)
|
log.Printf("when fetching: %s got: %s", path, err)
|
||||||
@ -78,7 +65,7 @@ func getStatic(w http.ResponseWriter, r *http.Request) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else { // anything else
|
||||||
otherStatic, err := webFS.ReadFile("data/" + path)
|
otherStatic, err := webFS.ReadFile("data/" + path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -96,200 +83,7 @@ func getStatic(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO there should be locks around all these config accesses
|
func (ws *WebService) getLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
func getSetWebhook(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
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")
|
|
||||||
config.SaveConfig()
|
|
||||||
postResponse := valueStringResponse{Success: true, Value: config.Config.WebHookURL}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO there should be locks around all these config accesses
|
|
||||||
func getSetUsername(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if r.Method == "GET" {
|
|
||||||
getResponse := valueStringResponse{Success: true, Value: config.Config.Username}
|
|
||||||
|
|
||||||
// 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.Username = r.PostForm.Get("value")
|
|
||||||
config.SaveConfig()
|
|
||||||
|
|
||||||
postResponse := valueStringResponse{Success: true, Value: config.Config.Username}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSetWatch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if r.Method == "GET" {
|
|
||||||
getResponse := valueStringResponse{Success: true, Value: strconv.Itoa(config.Config.Watch)}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.Atoi(r.PostForm.Get("value"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
response := errorResponse{Success: false, Error: fmt.Sprintf("Bad value for watch: %v", err)}
|
|
||||||
js, _ := json.Marshal(response)
|
|
||||||
w.Write(js)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < 1 {
|
|
||||||
response := errorResponse{Success: false, Error: "must be > 0"}
|
|
||||||
js, _ := json.Marshal(response)
|
|
||||||
w.Write(js)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Config.Watch = i
|
|
||||||
config.SaveConfig()
|
|
||||||
|
|
||||||
postResponse := valueStringResponse{Success: true, Value: strconv.Itoa(config.Config.Watch)}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSetNoWatermark(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if r.Method == "GET" {
|
|
||||||
getResponse := valueBooleanResponse{Success: true, Value: config.Config.NoWatermark}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := r.PostForm.Get("value")
|
|
||||||
|
|
||||||
if v != "0" && v != "1" {
|
|
||||||
response := errorResponse{Success: false, Error: fmt.Sprintf("Bad value for nowatermark: %v", err)}
|
|
||||||
js, _ := json.Marshal(response)
|
|
||||||
w.Write(js)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if v == "0" {
|
|
||||||
config.Config.NoWatermark = false
|
|
||||||
} else {
|
|
||||||
config.Config.NoWatermark = true
|
|
||||||
}
|
|
||||||
config.SaveConfig()
|
|
||||||
|
|
||||||
postResponse := valueBooleanResponse{Success: true, Value: config.Config.NoWatermark}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSetDirectory(w http.ResponseWriter, r *http.Request) {
|
|
||||||
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
|
|
||||||
config.SaveConfig()
|
|
||||||
|
|
||||||
postResponse := valueStringResponse{Success: true, Value: config.Config.Path}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSetExclude(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if r.Method == "GET" {
|
|
||||||
getResponse := valueStringResponse{Success: true, Value: config.Config.Exclude}
|
|
||||||
// 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.Exclude = r.PostForm.Get("value")
|
|
||||||
config.SaveConfig()
|
|
||||||
|
|
||||||
postResponse := valueStringResponse{Success: true, Value: config.Config.Exclude}
|
|
||||||
|
|
||||||
js, _ := json.Marshal(postResponse)
|
|
||||||
w.Write(js)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLogs(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
|
||||||
showDebug := false
|
showDebug := false
|
||||||
@ -312,29 +106,50 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(text))
|
w.Write([]byte(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUploads(w http.ResponseWriter, r *http.Request) {
|
func (ws *WebService) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
if r.Method == "POST" {
|
||||||
ups := uploads.Uploads
|
newConfig := config.ConfigV2{}
|
||||||
text, _ := json.Marshal(ups)
|
|
||||||
w.Write([]byte(text))
|
defer r.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte("bad body"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &newConfig)
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte("bad data"))
|
||||||
|
log.Printf("%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.Config.Config = newConfig
|
||||||
|
ws.Config.Save()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartWebServer() {
|
b, _ := json.Marshal(ws.Config.Config)
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
http.HandleFunc("/", getStatic)
|
// func getUploads(w http.ResponseWriter, r *http.Request) {
|
||||||
http.HandleFunc("/rest/config/webhook", getSetWebhook)
|
// w.Header().Set("Content-Type", "application/json")
|
||||||
http.HandleFunc("/rest/config/username", getSetUsername)
|
// ups := uploads.Uploads
|
||||||
http.HandleFunc("/rest/config/watch", getSetWatch)
|
// text, _ := json.Marshal(ups)
|
||||||
http.HandleFunc("/rest/config/nowatermark", getSetNoWatermark)
|
// w.Write([]byte(text))
|
||||||
http.HandleFunc("/rest/config/directory", getSetDirectory)
|
// }
|
||||||
http.HandleFunc("/rest/config/exclude", getSetExclude)
|
|
||||||
|
|
||||||
http.HandleFunc("/rest/logs", getLogs)
|
func (ws *WebService) StartWebServer() {
|
||||||
http.HandleFunc("/rest/uploads", getUploads)
|
|
||||||
|
http.HandleFunc("/", ws.getStatic)
|
||||||
|
|
||||||
|
http.HandleFunc("/rest/logs", ws.getLogs)
|
||||||
|
// http.HandleFunc("/rest/uploads", getUploads)
|
||||||
|
http.HandleFunc("/rest/config", ws.handleConfig)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Print("Starting web server on http://localhost:9090")
|
listen := fmt.Sprintf(":%d", ws.Config.Config.Port)
|
||||||
err := http.ListenAndServe(":9090", nil) // set listen port
|
log.Print("Starting web server on http://localhost%s", listen)
|
||||||
|
err := http.ListenAndServe(listen, nil) // set listen port
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("ListenAndServe: ", err)
|
log.Fatal("ListenAndServe: ", err)
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,19 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tardisx/discord-auto-upload/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHome(t *testing.T) {
|
func TestHome(t *testing.T) {
|
||||||
|
s := WebService{}
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
getStatic(w, req)
|
s.getStatic(w, req)
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
defer res.Body.Close()
|
|
||||||
data, err := ioutil.ReadAll(res.Body)
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected error to be nil got %v", err)
|
t.Errorf("expected error to be nil got %v", err)
|
||||||
}
|
}
|
||||||
@ -28,6 +32,7 @@ func TestHome(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNotFound(t *testing.T) {
|
func TestNotFound(t *testing.T) {
|
||||||
|
s := WebService{}
|
||||||
|
|
||||||
notFounds := []string{
|
notFounds := []string{
|
||||||
"/abc.html", "/foo.html", "/foo.html", "/../foo.html",
|
"/abc.html", "/foo.html", "/foo.html", "/../foo.html",
|
||||||
@ -38,8 +43,9 @@ func TestNotFound(t *testing.T) {
|
|||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, nf, nil)
|
req := httptest.NewRequest(http.MethodGet, nf, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
getStatic(w, req)
|
s.getStatic(w, req)
|
||||||
res := w.Result()
|
res := w.Result()
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
b, err := ioutil.ReadAll(res.Body)
|
b, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,3 +60,25 @@ func TestNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetConfig(t *testing.T) {
|
||||||
|
conf := config.DefaultConfigService()
|
||||||
|
conf.Config = *config.DefaultConfig()
|
||||||
|
s := WebService{Config: *conf}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/rest/config", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.handleConfig(w, req)
|
||||||
|
res := w.Result()
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected error to be nil got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(b) != `{"WatchInterval":10,"Version":2,"Watchers":[{"WebHookURL":"abcedf","Path":"/Users/justin/tmp","Username":"","NoWatermark":false,"Exclude":[]}]}` {
|
||||||
|
t.Errorf("Got unexpected response %v", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user