20 Commits
0.1 ... 0.4

Author SHA1 Message Date
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
Justin Hawkins
4e925136ba Bump version 2017-02-21 11:19:28 +10:30
Justin Hawkins
b37589985b Update checking, parsing of response, general cleanup 2017-02-21 11:15:12 +10:30
f60928fefb Fix typo 2017-02-20 21:58:55 +10:30
29fc0c67c9 Let's commit to the go version 2017-02-20 21:58:28 +10:30
6c3cb6066d Upload. Lacks all but the most basic of error checking 2017-02-20 21:54:16 +10:30
1870313424 Go version, for the easier cross-platform lulz 2017-02-20 21:16:44 +10:30
00218b6cc5 Fix typo 2017-02-18 21:53:44 +10:30
fcf206b999 Explicly add dependancy so PAR::Packer includes it 2017-02-18 21:50:58 +10:30
49c8ecd31a Merge branch 'master' of github.com:tardisx/discord-auto-upload 2017-02-16 22:47:10 +10:30
4c595d75d4 Improve documentation and trim output. 2017-02-16 22:46:29 +10:30
49d5ed58d0 Clarify requirements 2017-02-16 22:30:51 +10:30
77b7167d9c Update documenation on binary versions 2017-02-16 22:27:35 +10:30
6 changed files with 322 additions and 213 deletions

24
.gitignore vendored
View File

@@ -1,20 +1,4 @@
/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

View File

@@ -1,17 +0,0 @@
# Building "binaries"
For perl toolchain-free distribution.
Install PAR::Packer first, then:
## Mac
pp -M IO::Socket::SSL -o dau-mac dau
## Linux
pp -M IO::Socket::SSL -o dau-linux dau
## Windows
pp -M IO::Socket::SSL -o dau.exe dau

View File

@@ -1,6 +1,6 @@
# Automatically upload screenshots from your computer into a discord channel
This script automaticall uploads new screenshots that appear in a folder on your computer to Discord and posts them in a channel:
This program automatically uploads new screenshots that appear in a folder on your computer to Discord and posts them in a channel:
![Screenshot](http://i.imgur.com/QPS9V6f.jpg)
@@ -10,50 +10,17 @@ Point it at your Steam screenshot folder, or similar, and shortly after you hit
* A folder where screenshots are stored
* A [discord webhook](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks)
* This script
* perl installed (or the windows binary)
* This program
## Getting started
### Linux
### Binaries
* Download this script:
TBD
`curl -O https://raw.githubusercontent.com/tardisx/discord-auto-upload/master/dau`
#### From source
* Put it somewhere on your path (if you want to be able to run it from anywhere)
* chmod +x it
* Install the dependencies:
CPAN: `cpan install Mojolicious IO::Socket::SSL`
CPANM: `cpanm Mojolicious IO::Socket::SSL`
Ubuntu/Debian: `sudo apt-get install libmojolicious-perl libio-socket-ssl-perl`
* test it:
`dau --help`
### Mac
Basically the same as Linux above. [Perlbrew](https://perlbrew.pl) is highly recommended so as not to disturb the system perl. No need for superuser access then either.
### Windows
* Grab the windows exe file:
`http://tba`
* Optional, put it somewhere on your path
* Open a command prompt
* Test it
`\some\path\dau --help`
If you want to hack it, audit it, or don't trust my exe, you can install
[Strawberry Perl](http://strawberryperl.com) and run it using that directly.
You'll need the same dependencies mentioned above in the Linux setup.
TBD
## Using it
@@ -67,28 +34,30 @@ If `dau` is on your path, you can run it from your screenshot folder and there i
Note that currently `dau` does not look in subdirectories. Please submit an issue if this is a use case for you.
The only mandatory command line parameter is the discord webhook URL:
The only two mandatory command line parameters are the discord webhook URL:
`--webhook URL` - the webhook URL (see [here](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks) for details).
and the directory to watch:
`--directory /some/path/here` - the directory that screenshots will appear in.
You will have to quote the path on windows, or anywhere where the directory path contains spaces. Note that
subdirectories will also be scanned.
Other parameters are:
`--watch xx` - specify how many seconds to wait between scanning the directory. The default is 10 seconds.
`--directory <somedir>` - the directory to watch for images to appear in. If this option is not supplied, will look in the current directory.
`--username <username>` - an arbitrary string to show as the bot's username in the channel.
You will have to quote the path on windows, or anywhere where the directory path contains spaces.
`--help` - show command line help.
`--username` - supply a 'username' with the webhook submission. Slightly misleading, it basically provides some extra text next to the "Bot" display on the upload to the channel.
In the example screenshot, this was set to "tardisx uploaded from EDD".
`--debug` - provide extra debugging.
`--version` - show the version.
## Limitations/bugs
* Only files ending jpg, gif or png are uploaded.
* Subdirectories are not scanned.
* If multiple screenshots occur quickly (<1 second apart) not all may be uploaded.
## TODO

52
build-release.pl Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env perl
use strict;
use warnings;
open my $fh, "<", "dau.go" || die $!;
my $version;
while (<$fh>) {
$version = $1 if /^const\s+current_version.*?"([\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 => '386' }, filename => 'dau.exe' },
linux => { env => { GOOS => 'linux', GOARCH => '386' }, filename => 'dau' },
mac => { env => { GOOS => 'darwin', GOARCH => '386' }, filename => 'dau' },
);
foreach my $type (keys %build) {
mkdir "release/$type";
}
add_extras();
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 {
# bat file for windows
open (my $fh, ">", "release/win/dau.bat") || die $!;
print $fh 'set WEBHOOK_URL=https://yourdiscordwebhookURLhere' . "\r\n";
print $fh 'set SCREENSHOTS="C:\your\screenshot\directory\here"' ."\r\n";
print $fh 'set USERNAME="Posted by Joe Bloggs"' . "\r\n";
print $fh 'set WATCH=10' . "\r\n";
print $fh 'dau.exe --webhook %WEBHOOK_URL% --directory %SCREENSHOTS% --username %USERNAME% --watch %WATCH%' . "\r\n";
print $fh 'pause' . "\r\n";
close $fh;
}

128
dau
View File

@@ -1,128 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Mojo::UserAgent;
use Getopt::Long qw/GetOptions/;
use Data::Dumper qw/Dumper/;
my $webhook_url;
my $directory = "./";
my $username;
my $watch = 10;
my $help;
my $debug;
my $now = time();
my $error_count = 0;
my $error_max = 10;
my $version = '0.1';
GetOptions(
"webhook=s" => \$webhook_url,
"directory=s" => \$directory,
"username=s" => \$username,
"watch=i" => \$watch,
"help" => \$help,
"debug" => \$debug,
) || die usage();
usage() if $help;
if (! $webhook_url) {
usage("--webhook must be supplied");
}
sub usage {
my $error = shift || "";
my $indent = " " x length($0);
say "dau $version - https://github.com/tardisx/discord-auto-upload\n";
say "$0 --webhook <url> [--directory </some/path>]";
say "$indent [--username <\"custom username\">] [--watch <n>]\n";
say "The current directory will be used if no directory is specified.\n";
say "error: $error" if $error;
exit defined $error ? 1 : 0;
}
chdir $directory || die "cannot chdir to $directory: $!\n";
watch_dir();
sub watch_dir {
while (1) {
my @files = glob("*");
@files = grep { qualifies($_) } @files;
foreach my $file (sort { mtime($a) <=> mtime($b) } @files) {
debug("examining $file");
if (mtime($file) > $now) {
$now = mtime($file);
upload($file);
}
}
sleep $watch;
}
}
sub mtime {
my $f = shift;
return (stat($f))[9];
}
sub qualifies {
my $filename = shift;
return 1 if ($filename =~ /\.jpg$|\.gif$|\.png$/i);
return;
}
sub info {
say "- " . shift;
}
sub debug {
return unless $debug;
say "! " . shift;
}
sub error {
say "* " . shift;
}
sub upload {
my $file = shift;
info("uploading $file");
my $ua = Mojo::UserAgent->new;
my $data = {
upload => { file => $file },
$username ? ( username => $username ) : ()
};
my $tx = $ua->post($webhook_url, form => $data);
if (my $res = $tx->success) {
debug(Dumper($res->json));
my $url = $res->json->{attachments}->[0]->{url};
my $size = $res->json->{attachments}->[0]->{size};
my $width = $res->json->{attachments}->[0]->{width};
my $height = $res->json->{attachments}->[0]->{height};
info("uploaded ${width}x${height} $size bytes images to $url, submitted to webhook successfully");
}
else {
debug(Dumper($tx));
my $err = $tx->error;
if ($err->{code}) {
error("$err->{code} response: $err->{message}");
}
else {
error("Connection error: $err->{message}");
}
$error_count++;
if ($error_count >= $error_max) {
error("Sorry - too many errors - quitting");
}
}
}

249
dau.go Normal file
View File

@@ -0,0 +1,249 @@
package main
import (
"fmt"
"strings"
"github.com/pborman/getopt"
"path/filepath"
"os"
"time"
"net/http"
"log"
"io"
"bytes"
"mime/multipart"
"encoding/json"
"io/ioutil"
)
const current_version = "0.4"
var last_check = time.Now()
var new_last_check = time.Now()
var webhook_url string
var username string
func main() {
webhook_opt, path, watch, username_opt := parse_options()
webhook_url = webhook_opt
username = username_opt
check_path(path)
check_updates()
log.Print("Waiting for images to appear in ", path)
// wander the path, forever
for {
err := filepath.Walk(path, check_file)
if err != nil { log.Fatal("could not watch path", err) }
last_check = new_last_check
time.Sleep(time.Duration(watch)*time.Second)
}
}
func check_path(path string) {
src, err := os.Stat(path)
if err != nil {
log.Fatal(err)
}
if !src.IsDir() {
log.Fatal(path, " is not a directory")
os.Exit(1)
}
}
func check_updates() {
type GithubRelease struct {
Html_url string
Tag_name string
Name string
Body string
}
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) {
log.Fatal("could not check for updates:", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if (err != nil) {
log.Fatal("could not check read update response")
}
var latest GithubRelease
err = json.Unmarshal(body, &latest)
if (err != nil) {
log.Fatal("could not parse JSON: ", err)
}
if (current_version < latest.Tag_name) {
fmt.Printf("You are currently on version %s, but version %s is available\n", current_version, latest.Tag_name)
fmt.Println("----------- Release Info -----------")
fmt.Println(latest.Body)
fmt.Println("------------------------------------")
}
}
func parse_options() (webhook_url string, path string, watch int, username string) {
// Declare the flags to be used
webhookFlag := getopt.StringLong("webhook", 'w', "", "discord webhook URL")
pathFlag := getopt.StringLong("directory", 'd', "", "directory to scan, optional, defaults to current directory")
watchFlag := getopt.Int16Long ("watch", 's', 10, "time between scans")
usernameFlag := getopt.StringLong("username", 'u', "", "username for the bot upload")
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.Printf("Version: %s\n", current_version)
os.Exit(0)
}
if ! getopt.IsSet("directory") {
*pathFlag = "./"
log.Println("Defaulting to current directory")
}
if ! getopt.IsSet("webhook") {
log.Fatal("ERROR: You must specify a --webhook URL")
}
return *webhookFlag, *pathFlag, int(*watchFlag), *usernameFlag
}
func check_file(path string, f os.FileInfo, err error) error {
if f.ModTime().After(last_check) && f.Mode().IsRegular() {
if file_eligible(path) {
// process file
process_file(path)
}
if new_last_check.Before(f.ModTime()) {
new_last_check = f.ModTime()
}
}
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 process_file(file string) {
log.Print("Uploading ", file)
extraParams := map[string]string{ }
if (username != "") {
extraParams["username"] = username
}
type DiscordAPIResponseAttachment struct {
Url string
Proxy_url string
Size int
Width int
Height int
Filename string
}
type DiscordAPIResponse struct {
Attachments []DiscordAPIResponseAttachment
Id int64 `json:",string"`
}
request, err := newfileUploadRequest(webhook_url, 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.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.Print("could not parse JSON: ", err)
fmt.Println("Response was:", string(res_body[:]))
return
}
if (len(res.Attachments) < 1) {
log.Print("bad response - no attachments?")
return
}
var a = res.Attachments[0]
elapsed := time.Since(start)
rate := float64(a.Size) / elapsed.Seconds() / 1024.0
log.Printf("Uploaded to %s %dx%d", a.Url, a.Width, a.Height)
log.Printf("id: %d, %d bytes transferred in %.2f seconds (%.2f KiB/s)", res.Id, a.Size, elapsed.Seconds(), rate)
}
}
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
}