67 Commits
0.2 ... 0.10

Author SHA1 Message Date
c47660addf Add uploads page 2021-06-08 22:20:11 +09:30
2d0e294af6 Fix title 2021-06-08 22:19:54 +09:30
283e0f3584 Bump version 2021-06-08 22:19:37 +09:30
9ef6ab71c7 Add upload package and update dependencies 2021-06-07 21:13:57 +09:30
701583d3fd Update year 2021-06-07 21:13:18 +09:30
0c156a19f0 Fix punctuation 2021-06-06 22:22:44 +09:30
cc54bb6469 Enable filtering by debug and automatic scroll to bottom 2021-06-06 17:15:38 +09:30
71c097e578 Update gitignore 2021-06-06 17:15:19 +09:30
d23e31c0e0 Bump version 2021-06-04 10:42:26 +09:30
9cb79a846e Umm, how did this ever work? 😫 2021-06-04 10:41:23 +09:30
a5ce0c7f63 Documenation update 2021-06-03 19:42:12 +09:30
bcc4e145a2 Limit number of log entries stored 2021-06-03 19:40:53 +09:30
d8c0b7d0ea Fix error popup 2021-06-03 19:40:43 +09:30
fdf70daba7 Improve log display, use a <pre> so it can be easily cut and pasted. 2021-06-03 19:36:48 +09:30
b69cdebf3b Send logs to web server for display there 2021-06-02 23:42:29 +09:30
9e22490fe2 Merge pull request #10 from NoahBohme/master
Open discord link in a new tab
2021-03-15 23:30:56 +10:30
NoahBohme
ae24f16631 Delete .github/workflows directory 2021-03-15 11:51:07 +01:00
NoahBohme
b69acac0d0 Create go.yml 2021-02-23 09:46:47 +01:00
noah
c833f185cc add link for discord webhook 2021-02-23 08:23:10 +01:00
noah
b3ee0d9d1d Open discord link in a new tab 2021-02-23 08:16:59 +01:00
23c0aa2a34 Add Exclude to the config as well. 2021-02-09 22:07:40 +10:30
2d1ac3c803 Clean up logging and improve web interface 2021-02-07 22:06:19 +10:30
942f81a378 Bump version 2021-02-07 22:05:49 +10:30
287efab257 We no longer create a .bat file. 2021-02-07 22:05:16 +10:30
2b159e5532 Update README for the new version 2021-02-07 22:05:01 +10:30
851f073e99 Read/Write config to $HOME 2021-02-07 11:42:13 +10:30
ec658520b7 go fmt 2021-02-07 08:10:27 +10:30
6b1867f35f Support noWatermark config 2021-02-07 08:10:06 +10:30
6e493522c8 Add username and watch period web configuration 2021-02-06 12:50:44 +10:30
4d09901fb3 Fix up the javascript for loading config data. 2021-02-03 21:43:23 +10:30
9c9d4e492a Wrapper for HTML pages and start of the config web interface 2021-02-02 22:09:18 +10:30
e1f5afa788 go fmt 2021-01-31 18:48:48 +10:30
46a0f5a187 Rework configuration to it's own package and make it available to web server. Start a template driven web interface. 2021-01-31 17:53:32 +10:30
55bb5a8bae Fix build instructions 2021-01-31 09:56:09 +10:30
c2b9bf410d Bring into 2021 2021-01-30 21:23:58 +10:30
a4f958f846 Continue web server integration 2020-03-26 11:40:35 +10:30
14f8fe1933 Fix go generate call 2020-03-20 05:51:25 +10:30
752ff42a19 Add asset generation. 2020-03-20 05:49:12 +10:30
1ef062d19c Generate assets when releasing 2017-07-27 22:11:15 +09:30
6f09841209 Ignore generated asset directory 2017-07-27 22:11:03 +09:30
4619bb5383 Automatically open web browser on startup 2017-07-27 22:04:47 +09:30
e240f5dbd0 Embedded assets 2017-07-27 22:04:32 +09:30
Justin Hawkins
f976777f40 Start fleshing out web server 2017-07-27 12:18:02 +09:30
80a905b7d6 Improve update check error and make it non-fatal 2017-07-26 22:44:56 +09:30
497d2e3e27 Basic web server startup 2017-07-26 22:40:21 +09:30
ab54ace0d2 Add empty web class for the upcoming build-in web server 2017-07-26 14:24:02 +09:30
450765145b Add note about existing file handling 2017-07-26 13:24:07 +09:30
3970c611a4 Add --exclude flag (to avoid uploading thumbnails) 2017-02-28 22:50:03 +10:30
d8dc3e4ea8 Fix stupid text 2017-02-28 22:21:22 +10:30
3693d94297 Fix for new version variable name 2017-02-28 22:14:41 +10:30
8ded2b2e2d Document the --no-watermark feature 2017-02-28 22:10:53 +10:30
e3e712d073 Add retries, with backoff 2017-02-28 22:07:57 +10:30
1ecac568f7 Mark watermarked images be uploaded, and make it an optional option. 2017-02-28 21:32:18 +10:30
82ba3be742 Hacky image watermarking (not yet complete) 2017-02-26 21:06:48 +10:30
Justin Hawkins
4825dc56e6 Refactor away some globals. This is probably still not very idiomatic. 2017-02-23 12:55:10 +10:30
65b9241492 Refactor according to lint 2017-02-22 21:13:07 +10:30
Justin Hawkins
73b33f5872 Improve doc 2017-02-22 16:48:29 +10:30
Justin Hawkins
cc0fee57c2 Sub directories are scanned 2017-02-22 16:47:50 +10:30
Justin Hawkins
05a3a0d09a Update README 2017-02-22 16:46:48 +10:30
Justin Hawkins
72588642b6 Fix .gitignore 2017-02-22 15:52:28 +10:30
Justin Hawkins
7ff4685a70 Simple release build script 2017-02-22 15:47:51 +10:30
Justin Hawkins
f6b92ee8bd Show github link in --version 2017-02-22 15:47:26 +10:30
Justin Hawkins
68d9ab7859 Check path before starting to prevent crash. Show id of upload. 2017-02-21 17:10:00 +10:30
Justin Hawkins
d2d7843b6f Show upload rate and speed 2017-02-21 16:22:34 +10:30
Justin Hawkins
13589535a8 Add timeouts for uploads and version check. 2017-02-21 14:57:10 +10:30
Justin Hawkins
cb1f1d1a05 Version and help commands. 2017-02-21 12:28:26 +10:30
Justin Hawkins
699ca9fcfc Add username support, clean up command line parsing, help and output 2017-02-21 12:24:14 +10:30
17 changed files with 1381 additions and 214 deletions

28
.gitignore vendored
View File

@@ -1,20 +1,8 @@
/blib/
/.build/
_build/
cover_db/
inc/
Build
!Build/
Build.bat
.last_cover_stats
/Makefile
/Makefile.old
/MANIFEST.bak
/META.yml
/META.json
/MYMETA.*
nytprof.out
/pm_to_blib
*.o
*.bs
/_eumm/
dist
release
discord-auto-upload
discord-auto-upload.exe
assets
*.png
*.jpg
.DS_Store

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 Justin Hawkins
Copyright (c) 2021 Justin Hawkins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Automatically upload screenshots from your computer into a discord channel
# Automatically upload screenshots into a discord channel
This program automatically uploads new screenshots that appear in a folder on your computer to Discord and posts them in a channel:
@@ -16,44 +16,77 @@ Point it at your Steam screenshot folder, or similar, and shortly after you hit
### Binaries
TBD
Binaries are available for Mac, Linux and Windows [here](https://github.com/tardisx/discord-auto-upload/releases/latest).
#### From source
TBD
You'll need to [download Go](https://golang.org/dl/), check the code out somewhere, run 'go generate' and then 'go build'.
## Using it
`dau` is a command line driven program. When executed, it will continually scan a directory for new images, and each time it finds one it will upload it to discord, via the discord web hook.
`dau` configuration is managed via its internal web interface. When the executable is run, you can visit
`http://localhost:9090` in your web browser to configure the it. Configuration persists across runs, it is
saved in a file called '.dau.json' in your home directory.
The first time you run it, you will need to configure at least the discord web hook and the watch path for
`dau` to be useful.
While running, `dau` will continually scan a directory for new images, and each time it finds one it will upload it to discord, via the discord web hook.
`dau` will only upload "new" screenshots, where "new" means a file that appears in a directory that it is watching, if it appears *after* it has started executing.
Thus, you do not have to worry about pointing `dau` at a directory full of images, it will only upload new ones.
If `dau` is on your path, you can run it from your screenshot folder and there is then no need to specify the path to your images.
## Configuration options
Note that currently `dau` does not look in subdirectories. Please submit an issue if this is a use case for you.
See the web interface at http://localhost:9090 to configure `dau`.
The only two mandatory command line parameters are the discord webhook URL:
### 'Discord WebHook URL'
`--webhook URL` - the webhook URL (see [here](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks) for details).
The webhook URL from Discord. See https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks
for more information on setting one up.
and the directory to watch:
### 'Bot Username'
`--directory /some/path/here` - the directory that screenshots will appear in.
This is completely optional and can be any arbitrary string. It makes the upload
appear to come from a different user (though this is visual only, and does not
actually hide the bot identity in any way). You might like to set it to your own
discord name.
You will have to quote the path on windows, or anywhere where the directory path contains spaces.
### 'Directory to watch'
Other parameters are:
This is the path that `dau` will periodically inspect, looking for new images.
Note that subdirectories are also scanned. You need to enter the full filesystem
path here.
`--watch xx` - specify how many seconds to wait between scanning the directory. The default is 10 seconds.
### 'Period between filesystem checks'
This is the number of seconds between which `dau` will look for new images.
### 'Do not watermark images'
This will disable the watermarking of images. I like it when you don't set this :-)
### 'Files to exclude'
This is a string to match against the filename to check for exclusions. The common
use case is to use 'thumbnail' or similar if your image directory contains additional
thumbnail files.
## Limitations/bugs
* Only files ending jpg, gif or png are uploaded.
* If multiple screenshots occur quickly (<1 second apart) not all may be uploaded.
* Files to upload are determined by the file modification time. If you drag and drop existing files they will
not be detected and uploaded. Only newly created files will be detected.
## Troubleshooting
Please check the "log" page on the web interface for information when things are
not working as you expect.
## TODO
This is just a quick hack. Open to suggestions on new features and improvements.
This is just a relatively quick hack. Open to suggestions on new features and improvements.
Open an [issue](https://github.com/tardisx/discord-auto-upload/issues/new) and let me know.
Please include any relevant logs from the console when reporting bugs.

43
build-release.pl Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env perl
use strict;
use warnings;
open my $fh, "<", "config/config.go" || die $!;
my $version;
while (<$fh>) {
$version = $1 if /^const\s+CurrentVersion.*?"([\d\.]+)"/;
}
close $fh;
die "no version?" unless defined $version;
# so lazy
system "rm", "-rf", "release", "dist";
system "mkdir", "release";
system "mkdir", "dist";
my %build = (
win => { env => { GOOS => 'windows', GOARCH => 'amd64' }, filename => 'dau.exe' },
linux => { env => { GOOS => 'linux', GOARCH => 'amd64' }, filename => 'dau' },
mac => { env => { GOOS => 'darwin', GOARCH => 'amd64' }, filename => 'dau' },
);
foreach my $type (keys %build) {
mkdir "release/$type";
}
add_extras();
system(qw{go generate});
foreach my $type (keys %build) {
local $ENV{GOOS} = $build{$type}->{env}->{GOOS};
local $ENV{GOARCH} = $build{$type}->{env}->{GOARCH};
system "go", "build", "-o", "release/$type/" . $build{$type}->{filename};
system "zip", "-j", "dist/dau-$type-$version.zip", ( glob "release/$type/*" );
}
sub add_extras {
# we used to have a .bat file here, but no longer needed
}

78
config/config.go Normal file
View File

@@ -0,0 +1,78 @@
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/mitchellh/go-homedir"
)
// Config for the application
var Config struct {
WebHookURL string
Path string
Watch int
Username string
NoWatermark bool
Exclude string
}
const CurrentVersion string = "0.10"
// Load the current config or initialise with defaults
func LoadOrInit() {
configPath := configPath()
daulog.SendLog(fmt.Sprintf("Trying to load config from %s", configPath), daulog.LogTypeDebug)
_, err := os.Stat(configPath)
if os.IsNotExist(err) {
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)
Config.WebHookURL = ""
Config.Path = homeDir() + string(os.PathSeparator) + "screenshots"
Config.Watch = 10
SaveConfig()
} else {
LoadConfig()
}
}
func LoadConfig() {
path := configPath()
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("cannot read config file %s: %s", path, err.Error())
}
err = json.Unmarshal([]byte(data), &Config)
if err != nil {
log.Fatalf("cannot decode config file %s: %s", path, err.Error())
}
}
func SaveConfig() {
daulog.SendLog("saving configuration", daulog.LogTypeInfo)
path := configPath()
jsonString, _ := json.Marshal(Config)
err := ioutil.WriteFile(path, jsonString, os.ModePerm)
if err != nil {
log.Fatalf("Cannot save config %s: %s", path, err.Error())
}
}
func homeDir() string {
dir, err := homedir.Dir()
if err != nil {
panic(err)
}
return dir
}
func configPath() string {
homeDir := homeDir()
return homeDir + string(os.PathSeparator) + ".dau.json"
}

156
data/config.html Normal file
View File

@@ -0,0 +1,156 @@
<main role="main" class="inner DAU">
<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>
<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="">
<div class="form-row align-items-center config-item" data-key="username">
<div class="col-sm-5 my-1">
<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="">
<div class="form-row align-items-center config-item" data-key="directory">
<div class="col-sm-5 my-1">
<span>Directory to watch</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="">
<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">&nbsp;</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>
<script>
function update_sadness () {
if ($('#input-nowatermark').prop('checked')) {
$('#sadness').css('visibility','');
}
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>

110
data/dau.css Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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);
}
.DAU-container {
max-width: 42em;
}
pre {
background-color: black;
color: aliceblue;
}
/*
* 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);
}

8
data/index.html Normal file
View File

@@ -0,0 +1,8 @@
<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="https://github.com/tardisx/discord-auto-upload" class="btn btn-lg btn-secondary" target="_blank">Learn more</a>
</p>
</main>

53
data/logs.html Normal file
View File

@@ -0,0 +1,53 @@
<main role="main" class="inner DAU">
<h1 class="DAU-heading">Logs</h1>
<p class="lead">Discord-auto-upload logs</p>
<div class="container">
<div class="row">
<div class="col-sm">
<button type="button" onClick="debug=1; get_logs();" class="btn btn-primary">all logs</button>
</div>
<div class="col-sm">
<button type="button" onClick="debug=0; get_logs();" class="btn btn-primary">no debug</button>
</div>
<div class="col-sm">
<button type="button" id="scroll-button" onClick="toggle_scroll();" class="btn btn-primary">disable auto-scroll</button>
</div>
</div>
</div>
<pre id="logs" class="text-left pre-scrollable">
</pre>
</main>
<script>
var debug = 0;
var scrl = true;
$(document).ready(function() {
get_logs();
setInterval(function() { get_logs(); }, 1000);
});
function toggle_scroll() {
scrl = !scrl;
if (scrl) {
$('#scroll-button').text('disable auto-scroll');
}
else {
$('#scroll-button').text('auto-scroll');
}
}
function get_logs() {
$.ajax({ method: 'get', url: '/rest/logs', data: { debug : debug }})
.done(function(data) {
$('#logs').text(data);
console.log('scrl is ', scrl);
if (scrl) {
$('#logs').scrollTop(10000);
}
});
}
</script>

39
data/uploads.html Normal file
View File

@@ -0,0 +1,39 @@
<main role="main" class="inner DAU">
<h1 class="DAU-heading">Uploads</h1>
<p class="lead">Discord-auto-upload uploads</p>
<table class="table table-condensed table-dark">
<thead>
<tr><th>uploaded</th><th>dt</th><th>thumb</th></tr>
</thead>
<tbody id="uploads">
</tbody>
</table>
</main>
<script>
$(document).ready(function() {
get_uploads();
});
function get_uploads() {
$.ajax({ method: 'get', url: '/rest/uploads'})
.done(function(data) {
console.log(data);
$('#uploads').empty();
if (! data) { return }
data.forEach(i => {
// {uploaded: true, uploaded_at: "2021-06-08T21:59:52.855936+09:30", url: "https://cdn.discordapp.com/attachments/849615269706203171/851800197046468628/dau736004285.png", width: 640, height: 640}
console.log(i);
row = $('<tr>');
row.append($('<td>').text(i.uploaded ? 'yes' : 'no'));
row.append($('<td>').text(i.uploaded_at));
row.append($('<td>').html($('<img>', { width : i.width/10, height : i.height/10, src : i.url })));
$('#uploads').prepend(row);
});
});
}
</script>

60
data/wrapper.tmpl Normal file
View File

@@ -0,0 +1,60 @@
<!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.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" 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>
<a class="nav-link {{ if eq .Path "uploads.html"}} active {{ end }}" href="/uploads.html">Uploads</a>
<a class="nav-link {{ if eq .Path "logs.html"}} active {{ end }}" href="/logs.html">Logs</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>

316
dau.go
View File

@@ -1,213 +1,171 @@
package main
//go:generate go-bindata -pkg assets -o assets/static.go -prefix data/ data
import (
"fmt"
"strings"
"github.com/pborman/getopt"
"path/filepath"
"os"
"time"
"net/http"
"log"
"io"
"bytes"
"mime/multipart"
"encoding/json"
"io/ioutil"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/pborman/getopt"
// "github.com/skratchdot/open-golang/open"
"github.com/tardisx/discord-auto-upload/config"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/tardisx/discord-auto-upload/uploads"
"github.com/tardisx/discord-auto-upload/web"
)
var current_version = "0.2"
var last_check = time.Now()
var new_last_check = time.Now()
var webhook_url string
type webhook_response struct {
Test string
}
func keepLines(s string, n int) string {
result := strings.Join(strings.Split(s, "\n")[:n], "\n")
return strings.Replace(result, "\r", "", -1)
}
var lastCheck = time.Now()
var newLastCheck = time.Now()
func main() {
webhook, path, watch := parse_options()
webhook_url = webhook
check_updates()
parseOptions()
// wander the path, forever
for {
err := filepath.Walk(path, check_file)
if err != nil { log.Fatal("oh dear") }
//fmt.Printf("filepath.Walk() returned %v\n", err)
last_check = new_last_check
time.Sleep(time.Duration(watch)*time.Second)
}
// log.Print("Opening web browser")
// open.Start("http://localhost:9090")
web.StartWebServer()
checkUpdates()
daulog.SendLog(fmt.Sprintf("Waiting for images to appear in %s", config.Config.Path), daulog.LogTypeInfo)
// wander the path, forever
for {
if checkPath(config.Config.Path) {
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
}
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)
}
}
func check_updates() {
func checkPath(path string) bool {
src, err := os.Stat(path)
if err != nil {
log.Printf("Problem with path '%s': %s", path, err)
return false
}
if !src.IsDir() {
log.Printf("Problem with path '%s': is not a directory", path)
return false
}
return true
}
type GithubRelease struct {
Html_url string
Tag_name string
Name string
Body string
}
func checkUpdates() {
resp, err := http.Get("https://api.github.com/repos/tardisx/discord-auto-upload/releases/latest")
if (err != nil) {
log.Fatal("could not check for updates")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
log.Fatal("could not check read update response")
}
type GithubRelease struct {
HTMLURL string `json:"html_url"`
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
}
var latest GithubRelease
err = json.Unmarshal(body, &latest)
daulog.SendLog("checking for new version", daulog.LogTypeInfo)
if (err != nil) {
log.Fatal("could not parse JSON", err)
}
client := &http.Client{Timeout: time.Second * 5}
resp, err := client.Get("https://api.github.com/repos/tardisx/discord-auto-upload/releases/latest")
if err != nil {
daulog.SendLog(fmt.Sprintf("WARNING: Update check failed: %v", err), daulog.LogTypeError)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("could not check read update response")
}
if (current_version != latest.Tag_name) {
fmt.Println("A new version is available:", latest.Tag_name)
fmt.Println("----------- Release Info -----------")
fmt.Println(latest.Body)
fmt.Println("------------------------------------")
fmt.Println("( You are currently on version:", current_version, ")")
}
var latest GithubRelease
err = json.Unmarshal(body, &latest)
if err != nil {
log.Fatal("could not parse JSON: ", err)
}
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("------------------------------------")
fmt.Println("Upgrade at https://github.com/tardisx/discord-auto-upload/releases/latest")
daulog.SendLog(fmt.Sprintf("New version available: %s - download at https://github.com/tardisx/discord-auto-upload/releases/latest", latest.TagName), daulog.LogTypeInfo)
}
}
func parseOptions() {
func parse_options() (webhook_url string, path string, watch int) {
// Declare the flags to be used
helpFlag := getopt.BoolLong("help", 'h', "help")
versionFlag := getopt.BoolLong("version", 'v', "show version")
getopt.SetParameters("")
// Declare the flags to be used
// helpFlag := getopt.Bool('h', "display help")
webhookFlag := getopt.StringLong("webhook", 'w', "", "webhook URL")
pathFlag := getopt.StringLong("directory", 'd', "", "directory")
watchFlag := getopt.Int16Long("watch", 's', 10, "time between scans")
getopt.Parse()
getopt.Parse()
if *helpFlag {
getopt.PrintUsage(os.Stderr)
os.Exit(0)
}
return *webhookFlag, *pathFlag, int(*watchFlag)
if *versionFlag {
fmt.Println("dau - https://github.com/tardisx/discord-auto-upload")
fmt.Printf("Version: %s\n", config.CurrentVersion)
os.Exit(0)
}
// grab the config
config.LoadOrInit()
}
func check_file(path string, f os.FileInfo, err error) error {
// fmt.Println("Comparing", f.ModTime(), "to", last_check, "for", path)
func checkFile(path string, f os.FileInfo, err error) error {
if f.ModTime().After(lastCheck) && f.Mode().IsRegular() {
if f.ModTime().After(last_check) && f.Mode().IsRegular() {
if fileEligible(path) {
// process file
processFile(path)
}
if file_eligible(path) {
// process file
process_file(path)
}
if newLastCheck.Before(f.ModTime()) {
newLastCheck = f.ModTime()
}
}
if new_last_check.Before(f.ModTime()) {
new_last_check = f.ModTime()
}
}
return nil
return nil
}
func file_eligible(file string) (bool) {
extension := strings.ToLower(filepath.Ext(file))
if extension == ".png" || extension == ".jpg" || extension == ".gif" {
return true
}
return false
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 process_file(file string) {
log.Print("Uploading ", file)
extraParams := map[string]string{
// "username": "Some username",
}
type DiscordAPIResponseAttachment struct {
Url string
Proxy_url string
Size int
Width int
Height int
Filename string
}
type DiscordAPIResponse struct {
Attachments []DiscordAPIResponseAttachment
id int64
}
request, err := newfileUploadRequest(webhook_url, extraParams, "file", file)
if err != nil {
log.Fatal(err)
}
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
log.Fatal("Error performing request:", err)
} else {
if (resp.StatusCode != 200) {
log.Print("Bad response from server:", resp.StatusCode)
return
}
res_body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
log.Fatal("could not deal with body", err)
}
resp.Body.Close()
var res DiscordAPIResponse
err = json.Unmarshal(res_body, &res)
if (err != nil) {
log.Fatal("could not parse JSON", err)
fmt.Println("Response was:", res_body)
return
}
if (len(res.Attachments) < 1) {
log.Print("bad response - no attachments?")
return
}
var a = res.Attachments[0]
log.Printf("Uploaded to %s %dx%d, %d bytes\n", a.Url, a.Width, a.Height, a.Size)
}
func processFile(file string) {
}
func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
for key, val := range params {
_ = writer.WriteField(key, val)
}
err = writer.Close()
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", uri, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, err
daulog.SendLog("Sending to uploader", daulog.LogTypeInfo)
uploads.AddFile(file)
}

11
go.mod Normal file
View File

@@ -0,0 +1,11 @@
module github.com/tardisx/discord-auto-upload
go 1.15
require (
github.com/fogleman/gg v1.3.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/pborman/getopt v1.1.0
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
)

11
go.sum Normal file
View File

@@ -0,0 +1,11 @@
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

45
log/log.go Normal file
View File

@@ -0,0 +1,45 @@
package log
import (
"time"
)
type LogEntryType string
type LogEntry struct {
Timestamp time.Time `json:"ts"`
Type LogEntryType `json:"type"`
Entry string `json:"log"`
}
const (
LogTypeInfo = "info"
LogTypeError = "error"
LogTypeDebug = "debug"
)
var LogEntries []LogEntry
var logInput chan LogEntry
func init() {
// wait for log entries
logInput = make(chan LogEntry)
go func() {
for {
aLog := <-logInput
LogEntries = append(LogEntries, aLog)
for len(LogEntries) > 100 {
LogEntries = LogEntries[1:]
}
}
}()
}
func SendLog(entry string, entryType LogEntryType) {
logInput <- LogEntry{
Timestamp: time.Now(),
Entry: entry,
Type: entryType,
}
}

249
uploads/uploads.go Normal file
View File

@@ -0,0 +1,249 @@
// The uploads pacakge encapsulates dealing with file uploads to
// discord
package uploads
import (
"bytes"
"encoding/json"
"fmt"
"image"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"time"
"github.com/fogleman/gg"
"github.com/tardisx/discord-auto-upload/config"
daulog "github.com/tardisx/discord-auto-upload/log"
"golang.org/x/image/font/inconsolata"
)
type Upload struct {
Uploaded bool `json:"uploaded"` // has this file been uploaded to discord
UploadedAt time.Time `json:"uploaded_at"`
originalFilename string // path on the local disk
mungedFilename string // post-watermark
Url string `json:"url"` // url on the discord CDN
Width int `json:"width"`
Height int `json:"height"`
}
var Uploads []*Upload
func AddFile(file string) {
thisUpload := Upload{
Uploaded: false,
originalFilename: file,
}
Uploads = append(Uploads, &thisUpload)
ProcessUpload(&thisUpload)
}
func ProcessUpload(up *Upload) {
file := up.originalFilename
if !config.Config.NoWatermark {
daulog.SendLog("Copying to temp location and watermarking ", daulog.LogTypeInfo)
file = mungeFile(file)
up.mungedFilename = file
}
if config.Config.WebHookURL == "" {
daulog.SendLog("WebHookURL is not configured - cannot upload!", daulog.LogTypeError)
return
}
extraParams := map[string]string{}
if config.Config.Username != "" {
daulog.SendLog("Overriding username with "+config.Config.Username, daulog.LogTypeInfo)
extraParams["username"] = config.Config.Username
}
type DiscordAPIResponseAttachment struct {
URL string
ProxyURL string
Size int
Width int
Height int
Filename string
}
type DiscordAPIResponse struct {
Attachments []DiscordAPIResponseAttachment
ID int64 `json:",string"`
}
var retriesRemaining = 5
for retriesRemaining > 0 {
request, err := newfileUploadRequest(config.Config.WebHookURL, extraParams, "file", file)
if err != nil {
log.Fatal(err)
}
start := time.Now()
client := &http.Client{Timeout: time.Second * 30}
resp, err := client.Do(request)
if err != nil {
log.Print("Error performing request:", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
} else {
if resp.StatusCode != 200 {
// {"message": "Request entity too large", "code": 40005}
log.Print("Bad response from server:", resp.StatusCode)
if b, err := ioutil.ReadAll(resp.Body); err == nil {
log.Print("Body:", string(b))
daulog.SendLog(fmt.Sprintf("Bad response: %s", string(b)), daulog.LogTypeError)
}
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Print("could not deal with body: ", err)
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
resp.Body.Close()
var res DiscordAPIResponse
err = json.Unmarshal(resBody, &res)
// {"id": "851092588608880670", "type": 0, "content": "", "channel_id": "849615269706203171", "author": {"bot": true, "id": "849615314274484224", "username": "abcdedf", "avatar": null, "discriminator": "0000"}, "attachments": [{"id": "851092588332449812", "filename": "dau480457962.png", "size": 859505, "url": "https://cdn.discordapp.com/attachments/849615269706203171/851092588332449812/dau480457962.png", "proxy_url": "https://media.discordapp.net/attachments/849615269706203171/851092588332449812/dau480457962.png", "width": 640, "height": 640, "content_type": "image/png"}], "embeds": [], "mentions": [], "mention_roles": [], "pinned": false, "mention_everyone": false, "tts": false, "timestamp": "2021-06-06T13:38:05.660000+00:00", "edited_timestamp": null, "flags": 0, "components": [], "webhook_id": "849615314274484224"}
daulog.SendLog(fmt.Sprintf("Response: %s", string(resBody[:])), daulog.LogTypeDebug)
if err != nil {
log.Print("could not parse JSON: ", err)
fmt.Println("Response was:", string(resBody[:]))
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
if len(res.Attachments) < 1 {
log.Print("bad response - no attachments?")
retriesRemaining--
sleepForRetries(retriesRemaining)
continue
}
var a = res.Attachments[0]
elapsed := time.Since(start)
rate := float64(a.Size) / elapsed.Seconds() / 1024.0
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)
up.Url = a.URL
up.Uploaded = true
up.Width = a.Width
up.Height = a.Height
up.UploadedAt = time.Now()
break
}
}
if !config.Config.NoWatermark {
daulog.SendLog(fmt.Sprintf("Removing temporary file: %s", file), daulog.LogTypeDebug)
os.Remove(file)
}
if retriesRemaining == 0 {
log.Fatal("Failed to upload, even after retries")
}
}
func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
return nil, err
}
_, err = io.Copy(part, file)
if err != nil {
log.Fatal("Could not copy: ", err)
}
for key, val := range params {
_ = writer.WriteField(key, val)
}
err = writer.Close()
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", uri, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
return req, err
}
func mungeFile(path string) string {
reader, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer reader.Close()
im, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
bounds := im.Bounds()
// var S float64 = float64(bounds.Max.X)
dc := gg.NewContext(bounds.Max.X, bounds.Max.Y)
dc.Clear()
dc.SetRGB(0, 0, 0)
dc.SetFontFace(inconsolata.Regular8x16)
dc.DrawImage(im, 0, 0)
dc.DrawRoundedRectangle(0, float64(bounds.Max.Y-18.0), 320, float64(bounds.Max.Y), 0)
dc.SetRGB(0, 0, 0)
dc.Fill()
dc.SetRGB(1, 1, 1)
dc.DrawString("github.com/tardisx/discord-auto-upload", 5.0, float64(bounds.Max.Y)-5.0)
tempfile, err := ioutil.TempFile("", "dau")
if err != nil {
log.Fatal(err)
}
tempfile.Close()
os.Remove(tempfile.Name())
actualName := tempfile.Name() + ".png"
dc.SavePNG(actualName)
return actualName
}
func sleepForRetries(retry int) {
if retry == 0 {
return
}
retryTime := (6-retry)*(6-retry) + 6
daulog.SendLog(fmt.Sprintf("Will retry in %d seconds (%d remaining attempts)", retryTime, retry), daulog.LogTypeError)
time.Sleep(time.Duration(retryTime) * time.Second)
}

325
web/server.go Normal file
View File

@@ -0,0 +1,325 @@
package web
import (
"encoding/json"
"fmt"
"log"
"mime"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"text/template"
"github.com/tardisx/discord-auto-upload/assets"
"github.com/tardisx/discord-auto-upload/config"
daulog "github.com/tardisx/discord-auto-upload/log"
"github.com/tardisx/discord-auto-upload/uploads"
)
// DAUWebServer - stuff for the web server
type DAUWebServer struct {
ConfigChange chan int
}
type valueStringResponse struct {
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) {
// haha this is dumb and I should change it
re := regexp.MustCompile(`[^a-zA-Z0-9\.]`)
path := r.URL.Path[1:]
sanitized_path := re.ReplaceAll([]byte(path), []byte("_"))
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) {
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")
showDebug := false
debug, present := r.URL.Query()["debug"]
if present && len(debug[0]) > 0 && debug[0] != "0" {
showDebug = true
}
text := ""
for _, log := range daulog.LogEntries {
if !showDebug && log.Type == daulog.LogTypeDebug {
continue
}
text = text + fmt.Sprintf(
"%-6s %-19s %s\n", log.Type, log.Timestamp.Format("2006-01-02 15:04:05"), log.Entry,
)
}
// js, _ := json.Marshal(daulog.LogEntries)
w.Write([]byte(text))
}
func getUploads(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
ups := uploads.Uploads
text, _ := json.Marshal(ups)
w.Write([]byte(text))
}
func StartWebServer() {
http.HandleFunc("/", getStatic)
http.HandleFunc("/rest/config/webhook", getSetWebhook)
http.HandleFunc("/rest/config/username", getSetUsername)
http.HandleFunc("/rest/config/watch", getSetWatch)
http.HandleFunc("/rest/config/nowatermark", getSetNoWatermark)
http.HandleFunc("/rest/config/directory", getSetDirectory)
http.HandleFunc("/rest/config/exclude", getSetExclude)
http.HandleFunc("/rest/logs", getLogs)
http.HandleFunc("/rest/uploads", getUploads)
go func() {
log.Print("Starting web server on http://localhost:9090")
err := http.ListenAndServe(":9090", nil) // set listen port
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}()
}