aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example5
-rw-r--r--.gitignore9
-rw-r--r--Cargo.lock1194
-rw-r--r--Cargo.toml28
-rw-r--r--cmd/main.go16
-rw-r--r--commands.go38
-rw-r--r--config.example.json11
-rw-r--r--downloader/download_manager.go112
-rw-r--r--downloader/downloader.go180
-rw-r--r--downloader/downloader_test.go22
-rw-r--r--downloader/util.go77
-rw-r--r--extramemes.go86
-rw-r--r--messagectx.go84
-rw-r--r--migrations/00000000000000_diesel_initial_setup/down.sql6
-rw-r--r--migrations/00000000000000_diesel_initial_setup/up.sql36
-rw-r--r--migrations/2018-02-15-031841_create_memes/down.sql18
-rw-r--r--migrations/2018-02-15-031841_create_memes/up.sql73
-rw-r--r--src/commands/db.rs5
-rw-r--r--src/commands/mod.rs95
-rw-r--r--src/commands/playback/mod.rs184
-rw-r--r--src/commands/playback/types.rs139
-rw-r--r--src/commands/sound.rs88
-rw-r--r--src/db/mod.rs63
-rw-r--r--src/db/models.rs65
-rw-r--r--src/db/schema.rs82
-rw-r--r--src/main.rs205
-rw-r--r--src/util.rs34
-rw-r--r--thulani.go190
-rw-r--r--util.go108
-rw-r--r--wav/dr_wav.h1960
-rw-r--r--wav/wav.go140
-rw-r--r--wav/wav_test.go25
32 files changed, 2326 insertions, 3052 deletions
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..e97de41
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+TARGET_GUILD=abc
+OWNER_ID=def
+VOICE_CHANNEL=ghi
+THULANI_CLIENT_ID=jkl
+THULANI_TOKEN=mno
diff --git a/.gitignore b/.gitignore
index 5a7d72d..2d48ea0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
-config.json
-*.pprof
-main
+
+/target/
+**/*.rs.bk
+.env
+.vscode
+*.log
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..3b511dd
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1194 @@
+[[package]]
+name = "aho-corasick"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "antidote"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "backtrace"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "byteorder"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cc"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "chrono"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "colored"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ctrlc"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "derive-error-chain"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "diesel"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel_derives 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "diesel_derives"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dotenv"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dotenv"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dotenv_codegen"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dotenv 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dotenv_codegen_impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dotenv_codegen_impl"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dotenv 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "error-chain"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "evzht9h3nznqzwl"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fern"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "httparse"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "hyper"
+version = "0.10.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hyper-native-tls"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "idna"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libsodium-sys"
+version = "0.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "memchr"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mime"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mime_guess"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miniz-sys"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "multipart"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime_guess 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "nix"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num_cpus"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "openssl"
+version = "0.9.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.25 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "opus"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "opus-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "owning_ref"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot_core 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "phf"
+version = "0.7.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.7.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.7.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.7.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pq-sys"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro-hack-impl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quote"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "regex"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "regex-syntax"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "safemem"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "schannel"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "security-framework"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serenity"
+version = "0.5.1"
+source = "git+https://github.com/zeyla/serenity?rev=b71d99#b71d99fde84135fa66f73c4817d340ffbe8bddae"
+dependencies = [
+ "base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "evzht9h3nznqzwl 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "multipart 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opus 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sodiumoxide 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha1"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "siphasher"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "smallvec"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "sodiumoxide"
+version = "0.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libsodium-sys 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "synom"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tempdir"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread-id"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thulani"
+version = "0.1.0"
+dependencies = [
+ "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dotenv_codegen 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fern 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serenity 0.5.1 (git+https://github.com/zeyla/serenity?rev=b71d99)",
+ "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "time"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "traitobject"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "typeable"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "typemap"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicase"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unsafe-any"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "url"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "utf8-ranges"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "uuid"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "version_check"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
+"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
+"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
+"checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2"
+"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
+"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
+"checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d"
+"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4"
+"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
+"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
+"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
+"checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0"
+"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
+"checksum chrono 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ba5f60682a4c264e7f8d77b82e7788938a76befdf949d4a98026d19099c9d873"
+"checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
+"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
+"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
+"checksum ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "653abc99aa905f693d89df4797fadc08085baee379db92be9f2496cefe8a6f2c"
+"checksum derive-error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92183014af72c63aea490e66526c712bf1066ac50f66c9f34824f02483ec1d98"
+"checksum diesel 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925325c57038f2f14c0413bdf6a92ca72acff644959d0a1a9ebf8d19be7e9c01"
+"checksum diesel_derives 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28e2b2605ac6a3b9a586383f5f8b2b5f1108f07a421ade965b266289d2805e79"
+"checksum dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a70de3c590ce18df70743cace1cf12565637a0b26fd8b04ef10c7d33fdc66cdc"
+"checksum dotenv 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eea1395d2df3b5344dc577809296d9578303296e8d105c408aa80ed67d598ef1"
+"checksum dotenv_codegen 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "106db107983c2d0c10a9601cdd0c88514df87515e18d24df87e7ae0e2a50c3fa"
+"checksum dotenv_codegen_impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a600a6c3ca6ff4d85b97125eaaba6f67fd50c0efff2ff91b2e04e64d4432944a"
+"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
+"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
+"checksum evzht9h3nznqzwl 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d52f92982af5248fb5062e81529b3e3a316d08689ff7cf421e5997243a7e1be8"
+"checksum fern 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b7c5e874ad519490806243e9a6dfff638f4822aecb1847390d2e82d4486f73c6"
+"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
+"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+"checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37"
+"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
+"checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8"
+"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
+"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
+"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
+"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121"
+"checksum libsodium-sys 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cbbc6e46017815abf8698de0ed4847fad45fd8cad2909ac38ac6de79673c1ad1"
+"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
+"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
+"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
+"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
+"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
+"checksum mime_guess 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dc7e82a15629bb4ecd9e72365bf33d1382be91e030f820edb8e2a21c02430da8"
+"checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4"
+"checksum multipart 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92f54eb45230c3aa20864ccf0c277eeaeadcf5e437e91731db498dbf7fbe0ec6"
+"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0"
+"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
+"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
+"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
+"checksum num-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7de20f146db9d920c45ee8ed8f71681fd9ade71909b48c3acbd766aa504cf10"
+"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
+"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
+"checksum openssl-sys 0.9.25 (registry+https://github.com/rust-lang/crates.io-index)" = "93b3cbfaccf11969aea8c2041bfafc43c81666c1ce673476e19395c92cc77bf4"
+"checksum opus 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c1ea29ab439d0852a1d991c9fa8fa8e3f31aa82f938c42ed442831d08ead0db"
+"checksum opus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fad8b294f482f7972fa466b1c64d5a564e4ee6975599d80483ee4fa83f25b6ec"
+"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
+"checksum parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e7f7c9857874e54afeb950eebeae662b1e51a2493666d2ea4c0a5d91dcf0412"
+"checksum parking_lot_core 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8c47785371ae3ca397fe9eb2350e5a3ac5cfd7d329f3d9ea8375e39f1a55f377"
+"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
+"checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
+"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
+"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
+"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
+"checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54"
+"checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0"
+"checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892"
+"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
+"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
+"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
+"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
+"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
+"checksum regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5be5347bde0c48cfd8c3fdc0766cdfe9d8a755ef84d620d6794c778c91de8b2b"
+"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
+"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
+"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5"
+"checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e"
+"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
+"checksum schannel 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "acece75e0f987c48863a6c792ec8b7d6c4177d4a027f8ccc72f849794f437016"
+"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332"
+"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead"
+"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
+"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
+"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
+"checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb"
+"checksum serenity 0.5.1 (git+https://github.com/zeyla/serenity?rev=b71d99)" = "<none>"
+"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
+"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
+"checksum smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44db0ecb22921ef790d17ae13a3f6d15784183ff5f2a01aa32098c7498d2b4b9"
+"checksum sodiumoxide 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bc02c0bc77ffed8e8eaef004399b825cf4fd8aa02d0af6e473225affd583ff4d"
+"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
+"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
+"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
+"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e"
+"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
+"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
+"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
+"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
+"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
+"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
+"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
+"checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6"
+"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
+"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
+"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
+"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
+"checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"
+"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
+"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
+"checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
+"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
+"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
+"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9444eb0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "thulani"
+version = "0.1.0"
+authors = ["Nathan Perry <avaglir@gmail.com>"]
+
+[features]
+default = ["diesel"]
+
+[dependencies]
+lazy_static = "1.0"
+error-chain = "0.11.0"
+log = "0.4"
+typemap = "0.3.3"
+url = "1.6.0"
+dotenv = "0.11.0"
+dotenv_codegen = "0.11.0"
+chrono = "0.4.1"
+fern = { version = "0.5", features = ["colored"] }
+diesel = { version = "1.0.0", features = ["postgres", "chrono"], optional = true }
+cfg-if = "0.1"
+ctrlc = { version = "3.0", features = ["termination"] }
+
+[dependencies.serenity]
+# version = "~0.5"
+default-features = false
+features = ["builder", "cache", "client", "framework", "model", "utils", "voice", "standard_framework"]
+git = "https://github.com/zeyla/serenity"
+rev = "b71d99"
diff --git a/cmd/main.go b/cmd/main.go
deleted file mode 100644
index 2219caf..0000000
--- a/cmd/main.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package main
-
-import (
- "log"
-
- "github.com/mammothbane/thulani-go"
-)
-
-func main() {
- conf, err := thulani.LoadConfig("config.json")
- if err != nil {
- log.Fatal(err)
- }
-
- thulani.Run(conf)
-}
diff --git a/commands.go b/commands.go
deleted file mode 100644
index 428e87a..0000000
--- a/commands.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package thulani
-
-import "github.com/mammothbane/thulani-go/downloader"
-
-var cmdMap = map[string]func(*messageCtx){
- "help": printHelp,
- "skip": skip,
- "pause": pause,
- "resume": resume,
- "sudoku": stop,
- "die": stop,
- "list": list,
- "queue": list,
-}
-
-func printHelp(c *messageCtx) {
- c.sendMessage(help, c.Tts)
-}
-
-func skip(_ *messageCtx) {
- manager.PlayState <- downloader.Play
-}
-
-func resume(_ *messageCtx) {
- manager.PlayState <- downloader.Play
-}
-
-func pause(_ *messageCtx) {
- manager.PlayState <- downloader.Pause
-}
-
-func stop(_ *messageCtx) {
- manager.PlayState <- downloader.Clear
-}
-
-func list(_ *messageCtx) {
- log.Error("list not implemented")
-}
diff --git a/config.example.json b/config.example.json
deleted file mode 100644
index e4a7d79..0000000
--- a/config.example.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "trigger": "bot",
- "queue_size": 5,
- "admin_user_id": 12345,
- "op_role_id": 12345,
- "guild_id": 12345,
- "voice_channel_id": 12345,
- "token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "client_id": "abcd",
- "client_secret": "efgh"
-}
diff --git a/downloader/download_manager.go b/downloader/download_manager.go
deleted file mode 100644
index 16054ae..0000000
--- a/downloader/download_manager.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package downloader
-
-import (
- "time"
-
- "github.com/bwmarrin/discordgo"
-)
-
-type DlMessage int
-
-const (
- Clear DlMessage = iota
- Pause
- Play
- Skip
-)
-
-type playBundle struct {
- data <-chan []byte
- conn *discordgo.VoiceConnection
-}
-
-type DownloadManager struct {
- session *discordgo.Session
- guildID string
- voiceID string
- dls chan *downloader
-
- PlayState chan DlMessage
-}
-
-func NewManager(s *discordgo.Session, guildID string, voiceChanID string) *DownloadManager {
- dm := &DownloadManager{
- session: s,
- dls: make(chan *downloader),
- PlayState: make(chan DlMessage),
- guildID: guildID,
- voiceID: voiceChanID,
- }
-
- go dm.playFromQueue()
-
- return dm
-}
-
-func (m *DownloadManager) playFromQueue() {
- for {
- select {
- case <-m.PlayState: // ignore play state updates while not playing anything
- case dl := <-m.dls:
- conn, err := m.session.ChannelVoiceJoin(m.guildID, m.voiceID, false, false)
- if err != nil {
- log.Errorf("unable to connect to the voice channel: %q", err)
- time.Sleep(1 * time.Second)
- break
- }
-
- m.session.UpdateStatus(0, dl.info.Url.Host)
- out := dl.Start()
-
- playState := Play
- conn.Speaking(true)
-
- cleanup := func() {
- conn.Speaking(false)
- conn.Disconnect()
- m.session.UpdateStatus(0, "literally nothing")
- }
-
- inner:
- for {
- switch playState {
- case Clear:
- dl.Stop()
- for {
- select {
- case dl := <-m.dls:
- dl.Stop()
- dl.Start() // this is a hacky way of ensuring all the wavs are closed and cleaned up
- default:
- }
- }
- break inner
- case Skip:
- break inner
- case Pause:
- playState = <-m.PlayState
- case Play:
- select { // first check if we have a state update message coming in
- case playState = <-m.PlayState:
- case elem, ok := <-out:
- if !ok {
- break inner
- }
-
- conn.OpusSend <- elem
- }
- }
- }
- cleanup()
- }
- }
-}
-
-func (m *DownloadManager) Enqueue(url string, startTime, duration time.Duration) error {
- dl, err := newDownload(url, startTime, duration)
- if err != nil {
- return err
- }
- m.dls <- dl
- return nil
-}
diff --git a/downloader/downloader.go b/downloader/downloader.go
deleted file mode 100644
index b7645da..0000000
--- a/downloader/downloader.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package downloader
-
-import (
- "io/ioutil"
- "os"
- "os/exec"
- "strconv"
- "sync"
- "time"
-
- "github.com/mammothbane/thulani-go/wav"
-)
-
-// downloader handles a download for a particular song.
-type downloader struct {
- Url string
-
- StartTime time.Duration
- Duration time.Duration
- EndTime time.Duration
-
- once sync.Once
- done chan struct{}
- pb chan *wavBundle
-
- info videoInfo
-}
-
-const clipTime = 10 * time.Second
-const preloadCount = 5
-
-func newDownload(url string, startTime, dur time.Duration) (*downloader, error) {
- vInfo, err := info(url)
- if err != nil {
- return nil, err
- }
-
- if dur == 0 {
- dur = vInfo.Duration - startTime
- }
-
- dl := &downloader{
- Url: url,
-
- StartTime: startTime,
- Duration: dur,
- EndTime: startTime + dur,
-
- done: make(chan struct{}, 1),
- pb: make(chan *wavBundle, preloadCount),
- info: *vInfo,
- }
-
- go dl.schedule()
-
- return dl, nil
-}
-
-func (d *downloader) Stop() {
- d.once.Do(func() {
- close(d.done)
- })
-}
-
-func (d *downloader) Start() <-chan []byte {
- out := make(chan []byte, 1024)
-
- go func() {
- defer close(out)
- select {
- case <-d.done:
- for wavB := range d.pb {
- wavB.wav.Stop()
- wavB.cleanup()
- }
- return
- default:
- }
-
- for wavB := range d.pb {
- wavB.wav.Start(out)
-
- select {
- case <-d.done:
- wavB.wav.Stop()
- wavB.cleanup()
-
- case <-wavB.wav.Done:
- }
- }
- }()
-
- return out
-}
-
-func (d *downloader) schedule() {
- defer close(d.pb)
- for i := 0; ; i++ {
- select {
- case <-d.done:
- return
- default:
- }
-
- clipStart := time.Duration(i)*clipTime + d.StartTime
- clipEnd := time.Duration(i+1)*clipTime + d.StartTime
-
- if clipStart >= d.EndTime {
- return
- }
-
- dur := clipTime
- if clipEnd > d.EndTime {
- dur = d.EndTime - clipStart
- }
-
- wavb, err := d.downloadSegment(clipStart, dur)
- if err != nil {
- log.Errorf("error setting up download: %q", err)
- return
- }
-
- select {
- case d.pb <- wavb:
- case <-d.done:
- return
- }
- }
-}
-
-func (d *downloader) downloadSegment(startTime, duration time.Duration) (*wavBundle, error) {
- startSecond := int(startTime.Seconds())
- args := []string{
- "-ss", strconv.Itoa(startSecond),
- "-i", d.info.Url.String(),
- "-c:a", "pcm_s16le",
- "-f", "wav",
- "-ar", "48000",
- "-ac", "2",
- "-vn", "-y",
- }
-
- dur := int(duration.Seconds())
- if dur > 0 && startTime+duration < d.info.Duration {
- args = append(args, "-t", strconv.Itoa(dur))
- }
-
- file, err := ioutil.TempFile("", "thulani_")
- if err != nil {
- return nil, err
- }
-
- clearTemp := func() {
- if err := file.Close(); err != nil {
- log.Errorf("error closing temp file: %q", err)
- }
-
- if err := os.Remove(file.Name()); err != nil {
- log.Errorf("unable to remove temp file: %q", err)
- }
- }
-
- args = append(args, file.Name())
-
- dl := exec.Command(`ffmpeg`, args...)
- b, err := dl.CombinedOutput()
- if err != nil {
- clearTemp()
- log.Errorf("ffmpeg failed: \n%v", string(b))
- return nil, err
- }
-
- wv, err := wav.New(file.Name())
- if err != nil {
- clearTemp()
- return nil, err
- }
-
- return &wavBundle{wav: wv, cleanup: clearTemp}, err
-}
diff --git a/downloader/downloader_test.go b/downloader/downloader_test.go
deleted file mode 100644
index f38e1d0..0000000
--- a/downloader/downloader_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package downloader
-
-import (
- "fmt"
- "testing"
- "time"
-)
-
-func TestGetUrl(t *testing.T) {
- u, err := getUrl("https://www.youtube.com/watch?v=_K13GJkGvDw")
- if err != nil {
- t.Fatal(err)
- }
-
- fmt.Println(u)
-}
-
-func TestDownload(t *testing.T) {
- if _, err := Download("https://www.youtube.com/watch?v=_K13GJkGvDw", 10*time.Second, 10*time.Second); err != nil {
- t.Fatal(err)
- }
-}
diff --git a/downloader/util.go b/downloader/util.go
deleted file mode 100644
index f744fad..0000000
--- a/downloader/util.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package downloader
-
-import (
- "encoding/json"
- "io/ioutil"
- "net/url"
- "os/exec"
- "time"
-
- "github.com/mammothbane/thulani-go/wav"
- "github.com/op/go-logging"
-)
-
-var log = logging.MustGetLogger("downloader")
-
-// responsible for decoding from youtube
-type videoInfo struct {
- Title string `json:"fulltitle"`
- UrlStr string `json:"url"`
- DurationSec int `json:"duration"`
- Url *url.URL `json:"-"`
- Duration time.Duration `json:"-"`
-}
-
-func info(inUrl string) (*videoInfo, error) {
- dl := exec.Command("youtube-dl", "-f", "bestaudio", "-x", "-j", inUrl)
-
- outpipe, err := dl.StdoutPipe()
- if err != nil {
- return nil, err
- }
-
- errpipe, err := dl.StderrPipe()
- if err != nil {
- return nil, err
- }
-
- err = dl.Start()
- if err != nil {
- log.Errorf("starting youtube-dl failed")
- return nil, err
- }
-
- o, ierr := ioutil.ReadAll(outpipe)
- if ierr != nil {
- log.Errorf("unable to read from output pipe")
- return nil, err
- }
-
- e, ierr := ioutil.ReadAll(errpipe)
- if ierr != nil {
- log.Errorf("unable to read from error pipe")
- return nil, err
- }
-
- if err := dl.Wait(); err != nil {
- log.Errorf("error:\n%v", string(e))
- return nil, err
- }
-
- v := videoInfo{}
- if err := json.Unmarshal(o, &v); err != nil {
- return nil, err
- }
-
- v.Duration = time.Duration(v.DurationSec) * time.Second
- v.Url, err = url.Parse(v.UrlStr)
-
- //tgt, err := url.Parse(string(o))
- //out := tgt.Scheme + "://" + tgt.Host + tgt.Path + "?" + tgt.Query().Encode()
- return &v, err
-}
-
-type wavBundle struct {
- wav *wav.Wav
- cleanup func()
-}
diff --git a/extramemes.go b/extramemes.go
deleted file mode 100644
index fb2f8f6..0000000
--- a/extramemes.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package thulani
-
-import (
- "math/rand"
- "regexp"
- "strings"
- "time"
-)
-
-func init() {
- rand.Seed(time.Now().UnixNano())
-}
-
-var extraMemes = []func(*messageCtx) MemeStatus{
- respondToFuckYou,
- respondToMeme,
- respondToRaaaaaaaaaaaay,
-}
-
-var hateMatch = []string{
- "suck",
- "fuck",
- "trash",
- "garbage",
- "stupid",
- "shit",
- "dick",
- "bitch",
- "hate",
-}
-
-var responses = []string{
- "WELL FUCK YOU TOO YOU PIECE OF SHIT",
- "**i'll fucking burst ye**",
- "memememexexxxxxxxxxxxwerp",
- "thulando madondo",
- "you are a memerman",
-}
-
-type MemeStatus int
-
-const (
- Continue MemeStatus = iota
- Interrupt
-)
-
-func respondToFuckYou(ctx *messageCtx) (result MemeStatus) {
- result = Continue
- content := strings.ToLower(ctx.Message.Content)
-
- if !strings.Contains(content, config.Trigger) {
- return
- }
-
- for _, v := range hateMatch {
- if strings.Contains(content, strings.ToLower(v)) {
- response := responses[rand.Intn(len(responses))]
-
- ctx.sendMessage(response, true)
- return
- }
- }
-
- return
-}
-
-func respondToMeme(ctx *messageCtx) MemeStatus {
- if !(ctx.Matched && ctx.Command == "meme") {
- return Continue
- }
-
- ctx.sendMessage("i am not yet capable of memeing.", false)
- return Interrupt
-}
-
-var ray = regexp.MustCompile("ra+y")
-
-// TODO: play the sound clip
-func respondToRaaaaaaaaaaaay(ctx *messageCtx) MemeStatus {
- if ctx.Matched && ray.MatchString(ctx.Command) {
- ctx.sendMessage(ctx.Command, true)
- return Interrupt
- }
-
- return Continue
-}
diff --git a/messagectx.go b/messagectx.go
deleted file mode 100644
index 6525531..0000000
--- a/messagectx.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package thulani
-
-import (
- "strings"
- "sync"
-
- "github.com/bwmarrin/discordgo"
-)
-
-type messageCtx struct {
- sync.Mutex
-
- *discordgo.Session
- *discordgo.MessageCreate
-
- Command string
- Matched bool
-
- Channel *discordgo.Channel
- Member *discordgo.Member
- Guild *discordgo.Guild
-}
-
-func newCtx(s *discordgo.Session, m *discordgo.MessageCreate) (*messageCtx, error) {
- matches := regex.FindStringSubmatch(m.Content)
- command := ""
-
- if len(matches) != 0 {
- command = strings.Split(matches[1], " ")[0]
- }
-
- channel, err := s.State.Channel(m.ChannelID)
- if err != nil {
- return nil, err
- }
-
- var (
- wg sync.WaitGroup
- guild *discordgo.Guild
- member *discordgo.Member
- gErr, mErr error
- )
-
- wg.Add(2)
- go func() {
- guild, gErr = s.State.Guild(channel.GuildID)
- defer wg.Done()
- }()
-
- go func() {
- member, mErr = s.GuildMember(channel.GuildID, m.Author.ID)
- defer wg.Done()
- }()
- wg.Wait()
-
- if gErr != nil {
- return nil, gErr
- }
-
- if mErr != nil {
- return nil, mErr
- }
-
- return &messageCtx{
- Session: s,
- MessageCreate: m,
-
- Command: command,
- Matched: len(matches) != 0,
-
- Channel: channel,
- Guild: guild,
- Member: member,
- }, nil
-}
-
-func (ctx *messageCtx) sendMessage(str string, tts bool) {
- if !tts {
- ctx.ChannelMessageSend(ctx.ChannelID, str)
- return
- }
-
- ctx.ChannelMessageSendTTS(ctx.ChannelID, str)
-}
diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql
new file mode 100644
index 0000000..a9f5260
--- /dev/null
+++ b/migrations/00000000000000_diesel_initial_setup/down.sql
@@ -0,0 +1,6 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
+DROP FUNCTION IF EXISTS diesel_set_updated_at();
diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql
new file mode 100644
index 0000000..d68895b
--- /dev/null
+++ b/migrations/00000000000000_diesel_initial_setup/up.sql
@@ -0,0 +1,36 @@
+-- This file was automatically created by Diesel to setup helper functions
+-- and other internal bookkeeping. This file is safe to edit, any future
+-- changes will be added to existing projects as new migrations.
+
+
+
+
+-- Sets up a trigger for the given table to automatically set a column called
+-- `updated_at` whenever the row is modified (unless `updated_at` was included
+-- in the modified columns)
+--
+-- # Example
+--
+-- ```sql
+-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
+--
+-- SELECT diesel_manage_updated_at('users');
+-- ```
+CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
+BEGIN
+ EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
+ FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
+BEGIN
+ IF (
+ NEW IS DISTINCT FROM OLD AND
+ NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
+ ) THEN
+ NEW.updated_at := current_timestamp;
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
diff --git a/migrations/2018-02-15-031841_create_memes/down.sql b/migrations/2018-02-15-031841_create_memes/down.sql
new file mode 100644
index 0000000..91d88ab
--- /dev/null
+++ b/migrations/2018-02-15-031841_create_memes/down.sql
@@ -0,0 +1,18 @@
+DROP TABLE audio_memes;
+DROP TABLE image_memes;
+DROP TABLE text_memes;
+
+DROP TABLE audio;
+DROP TABLE images;
+
+DROP INDEX audit_updated;
+DROP INDEX audit_updated_by;
+DROP INDEX audit_metadata;
+DROP INDEX audit_metadata_updated_by;
+
+DROP TABLE audit_records;
+
+DROP INDEX metadata_created;
+DROP INDEX metadata_created_by;
+
+DROP TABLE metadata;
diff --git a/migrations/2018-02-15-031841_create_memes/up.sql b/migrations/2018-02-15-031841_create_memes/up.sql
new file mode 100644
index 0000000..dc24710
--- /dev/null
+++ b/migrations/2018-02-15-031841_create_memes/up.sql
@@ -0,0 +1,73 @@
+CREATE TABLE metadata (
+ id SERIAL PRIMARY KEY,
+
+ created TIMESTAMP NOT NULL DEFAULT current_timestamp,
+ created_by BIGINT NOT NULL
+);
+
+CREATE INDEX metadata_created on metadata (created);
+CREATE INDEX metadata_created_by on metadata (created_by);
+
+
+CREATE TABLE audit_records (
+ id SERIAL PRIMARY KEY,
+
+ updated TIMESTAMP NOT NULL DEFAULT current_timestamp,
+ updated_by BIGINT NOT NULL,
+
+ metadata_id INTEGER REFERENCES metadata NOT NULL
+);
+
+CREATE INDEX audit_updated on audit_records (updated);
+CREATE INDEX audit_updated_by on audit_records (updated_by);
+CREATE INDEX audit_metadata on audit_records (metadata_id);
+
+CREATE INDEX audit_metadata_updated_by on audit_records (metadata_id, updated_by);
+
+
+CREATE TABLE images (
+ id SERIAL PRIMARY KEY,
+ data bytea NOT NULL,
+
+ metadata_id INTEGER REFERENCES metadata UNIQUE NOT NULL
+);
+
+
+CREATE TABLE audio (
+ id SERIAL PRIMARY KEY,
+ data bytea NOT NULL,
+
+ metadata_id INTEGER REFERENCES metadata UNIQUE NOT NULL
+);
+
+
+CREATE TABLE text_memes (
+ id SERIAL PRIMARY KEY,
+ title varchar UNIQUE NOT NULL,
+ content TEXT NOT NULL,
+ image_id INTEGER REFERENCES images NULL,
+ audio_id INTEGER REFERENCES audio NULL,
+
+ metadata_id INTEGER REFERENCES metadata UNIQUE NOT NULL,
+ UNIQUE(content, image_id, audio_id)
+);
+
+
+CREATE TABLE image_memes (
+ id SERIAL PRIMARY KEY,
+ title varchar UNIQUE NOT NULL,
+ image_id INTEGER REFERENCES images NOT NULL,
+
+ metadata_id INTEGER REFERENCES metadata UNIQUE NOT NULL,
+ UNIQUE(title, image_id)
+);
+
+
+CREATE TABLE audio_memes (
+ id SERIAL PRIMARY KEY,
+ title varchar UNIQUE NOT NULL,
+ audio_id INTEGER REFERENCES audio NOT NULL,
+
+ metadata_id INTEGER REFERENCES metadata UNIQUE NOT NULL,
+ UNIQUE(title, audio_id)
+);
diff --git a/src/commands/db.rs b/src/commands/db.rs
new file mode 100644
index 0000000..4e8293a
--- /dev/null
+++ b/src/commands/db.rs
@@ -0,0 +1,5 @@
+use super::*;
+
+command!(meme(_ctx, msg) {
+ send(msg.channel_id, "I am not yet capable of memeing", msg.tts)?;
+});
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
new file mode 100644
index 0000000..3a9cb66
--- /dev/null
+++ b/src/commands/mod.rs
@@ -0,0 +1,95 @@
+use {must_env_lookup, Result, TARGET_GUILD_ID};
+use serenity::framework::StandardFramework;
+use serenity::model::channel::Message;
+use serenity::model::id::ChannelId;
+use serenity::prelude::*;
+use serenity::voice::{LockedAudio, ytdl};
+use std::thread;
+use std::time::Duration;
+
+mod playback;
+mod sound;
+
+pub use self::sound::*;
+pub use self::playback::*;
+
+cfg_if! {
+ if #[cfg(feature = "diesel")] {
+ mod db;
+ pub use self::db::*;
+
+ fn register_db(f: StandardFramework) -> StandardFramework {
+ f
+ .command("meme", |c| c
+ .guild_only(true)
+ .help_available(false)
+ .cmd(meme))
+ }
+ } else {
+ fn register_db(f: StandardFramework) -> StandardFramework {
+ f
+ }
+ }
+}
+
+fn send(channel: ChannelId, text: &str, tts: bool) -> Result<()> {
+ channel.send_message(|m| m.content(text).tts(tts))?;
+ Ok(())
+}
+
+pub fn register_commands(f: StandardFramework) -> StandardFramework {
+ let f: StandardFramework = register_db(f);
+ f
+ .command("skip", |c| c
+ .desc("skip the rest of the current request")
+ .guild_only(true)
+ .cmd(skip))
+ .command("pause", |c| c
+ .desc("pause playback (currently broken)")
+ .guild_only(true)
+ .cmd(pause))
+ .command("resume", |c| c
+ .desc("resume playing (currently broken)")
+ .guild_only(true)
+ .cmd(resume))
+ .command("list", |c| c
+ .known_as("queue")
+ .desc("list playing and queued requests")
+ .guild_only(true)
+ .cmd(list))
+ .command("die", |c| c
+ .batch_known_as(vec!["sudoku", "stop"])
+ .desc("stop playing and empty the queue")
+ .guild_only(true)
+ .cmd(die))
+ .command("mute", |c| c
+ .desc("mute thulani (playback continues)")
+ .guild_only(true)
+ .cmd(mute))
+ .command("unmute", |c| c
+ .desc("unmute thulani")
+ .guild_only(true)
+ .cmd(unmute))
+ .command("play", |c| c
+ .desc("queue a request")
+ .guild_only(true)
+ .cmd(play))
+ .command("volume", |c| c
+ .desc("set playback volume")
+ .guild_only(true)
+ .cmd(volume))
+ .unrecognised_command(|ctx, msg, unrec| {
+ let url = match msg.content.split_whitespace().skip(1).next() {
+ Some(x) => x,
+ None => {
+ info!("received unrecognized command: {}", unrec);
+ let _ = send(msg.channel_id, "format your commands right. fuck you.", msg.tts);
+ return;
+ }
+ };
+
+ let _ = self::playback::_play(ctx, msg, &url);
+ })
+}
+
+
diff --git a/src/commands/playback/mod.rs b/src/commands/playback/mod.rs
new file mode 100644
index 0000000..1d4ee96
--- /dev/null
+++ b/src/commands/playback/mod.rs
@@ -0,0 +1,184 @@
+use super::*;
+
+pub use self::types::*;
+
+mod types;
+
+pub fn _play(ctx: &Context, msg: &Message, url: &str) -> Result<()> {
+ debug!("playing '{}'", url);
+ if !url.starts_with("http") {
+ send(msg.channel_id, "bAD LiNk", msg.tts)?;
+ return Ok(());
+ }
+
+ if url.contains("imgur") {
+ send(msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts)?;
+ return Ok(());
+ }
+
+ trace!("acquiring queue lock");
+
+ let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+ let mut play_queue = queue_lock.write().unwrap();
+
+ trace!("queue lock acquired");
+
+ play_queue.queue.push_back(PlayArgs{
+ initiator: msg.author.name.clone(),
+ url: url.to_owned(),
+ sender_channel: msg.channel_id,
+ });
+
+ Ok(())
+}
+
+command!(play(ctx, msg, args) {
+ if args.len() == 0 {
+ _resume(ctx, msg)?;
+ return Ok(());
+ }
+
+ let url = match args.single::<String>() {
+ Ok(url) => url,
+ Err(_) => {
+ send(msg.channel_id, "BAD LINK", msg.tts)?;
+ return Ok(());
+ }
+ };
+
+ _play(ctx, msg, &url)?;
+});
+
+command!(pause(ctx, msg) {
+ let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+
+ let done = || send(msg.channel_id, "r u srs", msg.tts);
+ let playing = {
+ let play_queue = queue_lock.read().unwrap();
+
+ let current_item = match play_queue.playing {
+ Some(ref x) => x,
+ None => {
+ done()?;
+ return Ok(());
+ },
+ };
+
+ let audio = current_item.audio.lock();
+ audio.playing
+ };
+
+ if !playing {
+ done()?;
+ return Ok(());
+ }
+
+ {
+ let queue = queue_lock.write().unwrap();
+ let ref audio = queue.playing.clone().unwrap().audio;
+ audio.lock().pause();
+ }
+});
+
+command!(resume(ctx, msg) {
+ _resume(ctx, msg)?;
+});
+
+fn _resume(ctx: &mut Context, msg: &Message) -> Result<()> {
+ let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+
+ let done = || send(msg.channel_id, "r u srs", msg.tts);
+ let playing = {
+ let play_queue = queue_lock.read().unwrap();
+
+ let current_item = match play_queue.playing {
+ Some(ref x) => x,
+ None => {
+ done()?;
+ return Ok(());
+ },
+ };
+
+ let audio = current_item.audio.lock();
+ audio.playing
+ };
+
+ if playing {
+ done()?;
+ return Ok(());
+ }
+
+ {
+ let queue = queue_lock.write().unwrap();
+ let ref audio = queue.playing.clone().unwrap().audio;
+ audio.lock().play();
+ }
+
+ Ok(())
+}
+
+command!(skip(ctx, _msg) {
+ let data = ctx.data.lock();
+
+ let mut mgr_lock = data.get::<VoiceManager>().cloned().unwrap();
+ let mut manager = mgr_lock.lock();
+
+ let mut queue_lock = data.get::<PlayQueue>().cloned().unwrap();
+
+ if let Some(handler) = manager.get_mut(*TARGET_GUILD_ID) {
+ handler.stop();
+ let mut play_queue = queue_lock.write().unwrap();
+ play_queue.playing = None;
+ } else {
+ debug!("got skip with no handler attached");
+ }
+});
+
+command!(die(ctx, msg) {
+ let data = ctx.data.lock();
+
+ let mut mgr_lock = data.get::<VoiceManager>().cloned().unwrap();
+ let mut manager = mgr_lock.lock();
+
+ let mut queue_lock = data.get::<PlayQueue>().cloned().unwrap();
+
+ {
+ let mut play_queue = queue_lock.write().unwrap();
+
+ play_queue.playing = None;
+ play_queue.queue.clear();
+ }
+
+ if let Some(handler) = manager.get_mut(*TARGET_GUILD_ID) {
+ handler.stop();
+ handler.leave();
+ } else {
+ send(msg.channel_id, "YOU die", msg.tts)?;
+ debug!("got die with no handler attached");
+ }
+});
+
+command!(list(ctx, msg) {
+ let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+ let mut play_queue = queue_lock.read().unwrap();
+
+ let channel_tmp = msg.channel().unwrap().guild().unwrap();
+ let channel = channel_tmp.read();
+
+ match play_queue.playing {
+ Some(ref info) => {
+ let audio = info.audio.lock();
+ let status = if audio.playing { "playing" } else { "paused:" };
+ send(msg.channel_id, &format!("Currently {} `{}` ({})", status, info.init_args.url, info.init_args.initiator), msg.tts)?;
+ },
+ None => {
+ debug!("`list` called with no items in queue");
+ send(msg.channel_id, "Nothing is playing you meme", msg.tts)?;
+ return Ok(());
+ },
+ }
+
+ play_queue.queue.iter().for_each(|info| {
+ channel.say(&format!("`{}` ({})", info.url, info.initiator)).unwrap();
+ });
+});
diff --git a/src/commands/playback/types.rs b/src/commands/playback/types.rs
new file mode 100644
index 0000000..41592ec
--- /dev/null
+++ b/src/commands/playback/types.rs
@@ -0,0 +1,139 @@
+use serenity::client::bridge::voice::ClientVoiceManager;
+use typemap::Key;
+use std::sync::{Arc, RwLock};
+use std::collections::VecDeque;
+use super::*;
+
+pub struct VoiceManager;
+
+impl Key for VoiceManager {
+ type Value = Arc<Mutex<ClientVoiceManager>>;
+}
+
+impl VoiceManager {
+ pub fn register(c: &mut Client) {
+ let mut data = c.data.lock();
+ data.insert::<VoiceManager>(Arc::clone(&c.voice_manager));
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct PlayArgs {
+ pub url: String,
+ pub initiator: String,
+ pub sender_channel: ChannelId,
+}
+
+#[derive(Clone)]
+pub struct CurrentItem {
+ pub init_args: PlayArgs,
+ pub audio: LockedAudio,
+}
+
+#[derive(Clone)]
+pub struct PlayQueue {
+ pub queue: VecDeque<PlayArgs>,
+ pub playing: Option<CurrentItem>,
+ pub volume: f32,
+}
+
+impl Key for PlayQueue {
+ type Value = Arc<RwLock<PlayQueue>>;
+}
+
+impl PlayQueue {
+ pub fn new() -> Self {
+ PlayQueue {
+ queue: VecDeque::new(),
+ playing: None,
+ volume: DEFAULT_VOLUME,
+ }
+ }
+
+ pub fn register(c: &mut Client) {
+ let voice_manager = Arc::clone(&c.voice_manager);
+
+ let mut data = c.data.lock();
+ let queue = Arc::new(RwLock::new(PlayQueue::new()));
+
+ data.insert::<PlayQueue>(Arc::clone(&queue));
+
+ thread::spawn(move || {
+ let queue_lck = Arc::clone(&queue);
+ let voice_manager = voice_manager;
+
+ loop {
+ thread::sleep(Duration::from_millis(250));
+ let (queue_is_empty, queue_has_playing) = {
+ let queue = queue_lck.read().unwrap();
+
+ let allow_continue = queue.playing.clone().map_or(false, |x| !x.audio.lock().finished);
+
+ if allow_continue {
+ continue;
+ }
+
+ (queue.queue.is_empty(), queue.playing.is_some())
+ };
+
+ if queue_is_empty {
+ if queue_has_playing {
+ let mut queue = queue_lck.write().unwrap();
+
+ assert!({
+ let audio_lck = queue.playing.clone().unwrap().audio;
+ let audio = audio_lck.lock();
+ audio.finished
+ });
+
+ queue.playing = None;
+
+ let mut manager = voice_manager.lock();
+ manager.leave(*TARGET_GUILD_ID);
+ debug!("disconnected due to inactivity");
+ }
+ continue;
+ }
+
+ let mut queue = queue_lck.write().unwrap();
+ let item = queue.queue.pop_front().unwrap();
+
+ trace!("checking ytdl for: {}", item.url);
+
+ let src = match ytdl(&item.url) {
+ Ok(src) => src,
+ Err(e) => {
+ error!("bad link: {}; {:?}", &item.url, e);
+ let _ = send(item.sender_channel, &format!("what the fuck"), false);
+ continue;
+ }
+ };
+
+ trace!("got ytdl item for {}", item.url);
+
+ let mut manager = voice_manager.lock();
+ let handler = manager.join(*TARGET_GUILD_ID, must_env_lookup::<u64>("VOICE_CHANNEL"));
+
+ match handler {
+ Some(handler) => {
+ let mut audio = handler.play_only(src);
+ {
+ audio.lock().volume(queue.volume);
+ }
+
+ queue.playing = Some(CurrentItem {
+ init_args: item,
+ audio,
+ });
+
+ debug!("playing new song");
+ },
+ None => {
+ error!("couldn't join channel");
+ let _ = send(item.sender_channel, "something happened somewhere somehow.", false);
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/src/commands/sound.rs b/src/commands/sound.rs
new file mode 100644
index 0000000..6cac61d
--- /dev/null
+++ b/src/commands/sound.rs
@@ -0,0 +1,88 @@
+use super::*;
+
+pub const DEFAULT_VOLUME: f32 = 0.05;
+
+command!(mute(ctx, _msg) {
+ let mut mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
+ let mut manager = mgr_lock.lock();
+
+ manager.get_mut(*TARGET_GUILD_ID)
+ .map(|handler| {
+ if handler.self_mute {
+ trace!("Already muted.")
+ } else {
+ handler.mute(true);
+ trace!("Muted");
+ }
+ });
+});
+
+command!(unmute(ctx, msg) {
+ let mut mgr_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
+ let mut manager = mgr_lock.lock();
+
+ manager.get_mut(*TARGET_GUILD_ID)
+ .map(|handler| {
+ if !handler.self_mute {
+ trace!("Already unmuted.")
+ } else {
+ handler.mute(false);
+ trace!("Unmuted");
+ let _ = send(msg.channel_id, "REEEEEEEEEEEEEE", msg.tts);
+ }
+ });
+});
+
+command!(volume(ctx, msg, args) {
+ if args.len() == 0 {
+ let vol = {
+ let queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+ let mut play_queue = queue_lock.read().unwrap();
+ (play_queue.volume / DEFAULT_VOLUME * 100.0) as usize
+ };
+
+ send(msg.channel_id, &format!("Volume: {}/100", vol), msg.tts)?;
+ return Ok(());
+ }
+
+ let mut vol: usize = match args.single::<f32>() {
+ Ok(vol) if vol.is_nan() => {
+ send(msg.channel_id, "you're a fuck", msg.tts)?;
+ return Ok(());
+ },
+ Ok(vol) => vol as usize,
+ Err(_) => {
+ send(msg.channel_id, "???????", msg.tts)?;
+ return Ok(());
+ },
+ };
+
+ let mut vol: f32 = (vol as f32)/100.0; // force aliasing to reasonable values
+
+ if vol > 3.0 {
+ vol = 3.0;
+ }
+
+ if vol < 0.0 {
+ vol = 0.0;
+ }
+
+ let mut queue_lock = ctx.data.lock().get::<PlayQueue>().cloned().unwrap();
+
+ {
+ let mut play_queue = queue_lock.write().unwrap();
+ play_queue.volume = vol * DEFAULT_VOLUME;
+ }
+
+ {
+ let play_queue = queue_lock.read().unwrap();
+
+ let current_item = match play_queue.playing {
+ Some(ref x) => x,
+ None => return Ok(()),
+ };
+
+ let mut audio = current_item.audio.lock();
+ audio.volume(play_queue.volume);
+ };
+});
diff --git a/src/db/mod.rs b/src/db/mod.rs
new file mode 100644
index 0000000..8ce0011
--- /dev/null
+++ b/src/db/mod.rs
@@ -0,0 +1,63 @@
+use std::env;
+
+use diesel::prelude::*;
+
+use super::{Result, Error};
+pub use self::models::*;
+
+mod schema;
+mod models;
+
+fn connection() -> Result<PgConnection> {
+ let database_url = env::var("DATABASE_URL")?;
+ PgConnection::establish(&database_url).map_err(Error::from)
+}
+
+pub fn find_text(search: String) -> Result<TextMeme> {
+ use self::schema::text_memes::dsl::*;
+
+ let format_search = format!("%{}%", search);
+
+ let conn = connection()?;
+ text_memes
+ .filter(title.ilike(&format_search).or(content.ilike(&format_search)))
+ .limit(1)
+ .first::<TextMeme>(&conn)
+ .map_err(Error::from)
+}
+
+pub fn find_audio(search: String) -> Result<AudioMeme> {
+ use self::schema::audio_memes::dsl::*;
+
+ let format_search = format!("%{}%", search);
+
+ let conn = connection()?;
+ audio_memes
+ .filter(title.ilike(format_search))
+ .limit(1)
+ .first::<AudioMeme>(&conn)
+ .map_err(Error::from)
+}
+
+pub fn rand_audio() -> Result<AudioMeme> {
+ use self::schema::audio_memes::dsl::*;
+
+ let conn = connection()?;
+ audio_memes
+ .order(random.desc())
+ .first::<AudioMeme>(&conn)
+ .map_err(Error::from)
+}
+
+pub fn rand_text() -> Result<TextMeme> {
+ use self::schema::text_memes::dsl::*;
+
+ let conn = connection()?;
+ text_memes
+ .order(random.desc())
+ .first::<TextMeme>(&conn)
+ .map_err(Error::from)
+}
+
+use diesel::sql_types;
+no_arg_sql_function!(random, sql_types::Double, "SQL random() function");
diff --git a/src/db/models.rs b/src/db/models.rs
new file mode 100644
index 0000000..c06c41e
--- /dev/null
+++ b/src/db/models.rs
@@ -0,0 +1,65 @@
+use super::schema::*;
+use chrono::naive::NaiveDateTime;
+
+#[derive(Insertable, Queryable, Identifiable, AsChangeset, Debug, Associations)]
+#[belongs_to(Audio)]
+#[belongs_to(Image)]
+#[belongs_to(TextMeme)]
+#[belongs_to(ImageMeme)]
+#[belongs_to(TextMeme)]
+#[table_name="metadata"]
+pub struct Metadata {
+ pub id: i32,
+ pub created: NaiveDateTime,
+ pub created_by: i64,
+}
+
+#[derive(Insertable, Queryable, Identifiable, PartialEq, AsChangeset, Debug, Associations)]
+#[belongs_to(AudioMeme)]
+#[belongs_to(TextMeme)]
+#[table_name="audio"]
+pub struct Audio {
+ pub id: i32,
+ pub data: Vec<u8>,
+ pub metadata_id: i32,
+}
+
+#[derive(Insertable, Queryable, Identifiable, PartialEq, AsChangeset, Debug, Associations)]
+#[belongs_to(ImageMeme)]
+#[belongs_to(TextMeme)]
+#[table_name="images"]
+pub struct Image {
+ pub id: i32,
+ pub data: Vec<u8>,
+ pub metadata_id: i32,
+}
+
+#[derive(Insertable, Queryable, Identifiable, PartialEq, AsChangeset, Debug)]
+#[table_name="audio_memes"]
+pub struct AudioMeme {
+ pub id: i32,
+ pub title: String,
+ pub audio_id: i32,
+ pub metadata_id: i32,
+}
+
+#[derive(Insertable, Queryable, Identifiable, PartialEq, AsChangeset, Debug)]
+#[table_name="text_memes"]
+pub struct TextMeme {
+ pub id: i32,
+ pub title: String,
+ pub content: String,
+ pub image_id: Option<i32>,
+ pub audio_id: Option<i32>,
+ pub metadata_id: i32,
+}
+
+#[derive(Insertable, Queryable, Identifiable, PartialEq, AsChangeset, Debug, Associations)]
+#[belongs_to(Metadata)]
+#[table_name="audit_records"]
+pub struct AuditRecord {
+ pub id: i32,
+ pub updated: NaiveDateTime,
+ pub updated_by: i64,
+ pub metadata_id: i32,
+}
diff --git a/src/db/schema.rs b/src/db/schema.rs
new file mode 100644
index 0000000..40891a5
--- /dev/null
+++ b/src/db/schema.rs
@@ -0,0 +1,82 @@
+table! {
+ audio (id) {
+ id -> Int4,
+ data -> Bytea,
+ metadata_id -> Int4,
+ }
+}
+
+table! {
+ audio_memes (id) {
+ id -> Int4,
+ title -> Varchar,
+ audio_id -> Int4,
+ metadata_id -> Int4,
+ }
+}
+
+table! {
+ audit_records (id) {
+ id -> Int4,
+ updated -> Timestamp,
+ updated_by -> Int8,
+ metadata_id -> Int4,
+ }
+}
+
+table! {
+ image_memes (id) {
+ id -> Int4,
+ title -> Varchar,
+ image_id -> Int4,
+ metadata_id -> Int4,
+ }
+}
+
+table! {
+ images (id) {
+ id -> Int4,
+ data -> Bytea,
+ metadata_id -> Int4,
+ }
+}
+
+table! {
+ metadata (id) {
+ id -> Int4,
+ created -> Timestamp,
+ created_by -> Int8,
+ }
+}
+
+table! {
+ text_memes (id) {
+ id -> Int4,
+ title -> Varchar,
+ content -> Text,
+ image_id -> Nullable<Int4>,
+ audio_id -> Nullable<Int4>,
+ metadata_id -> Int4,
+ }
+}
+
+joinable!(audio -> metadata (metadata_id));
+joinable!(audio_memes -> audio (audio_id));
+joinable!(audio_memes -> metadata (metadata_id));
+joinable!(audit_records -> metadata (metadata_id));
+joinable!(image_memes -> images (image_id));
+joinable!(image_memes -> metadata (metadata_id));
+joinable!(images -> metadata (metadata_id));
+joinable!(text_memes -> audio (audio_id));
+joinable!(text_memes -> images (image_id));
+joinable!(text_memes -> metadata (metadata_id));
+
+allow_tables_to_appear_in_same_query!(
+ audio,
+ audio_memes,
+ audit_records,
+ image_memes,
+ images,
+ metadata,
+ text_memes,
+);
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..fba574e
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,205 @@
+#[macro_use] extern crate cfg_if;
+extern crate chrono;
+extern crate ctrlc;
+extern crate dotenv;
+#[macro_use] extern crate dotenv_codegen;
+#[macro_use] extern crate error_chain;
+extern crate fern;
+#[macro_use] extern crate lazy_static;
+#[macro_use] extern crate log;
+#[macro_use] extern crate serenity;
+extern crate typemap;
+extern crate url;
+
+use commands::register_commands;
+use dotenv::dotenv;
+use errors::*;
+use serenity::framework::standard::help_commands;
+use serenity::framework::StandardFramework;
+use serenity::model::gateway::Ready;
+use serenity::model::id::{GuildId, UserId};
+use serenity::prelude::*;
+use std::env;
+use std::thread;
+use std::time::{Duration, Instant};
+pub use util::*;
+cfg_if! {
+ if #[cfg(feature = "diesel")] {
+ #[macro_use] extern crate diesel;
+ mod db;
+ }
+}
+
+mod commands;
+mod util;
+
+mod errors {
+ error_chain! {
+ foreign_links {
+ Serenity(::serenity::Error);
+ MissingVar(::std::env::VarError);
+ DieselConn(::diesel::ConnectionError) #[cfg(feature = "diesel")];
+ Diesel(::diesel::result::Error) #[cfg(feature = "diesel")];
+ }
+ }
+}
+
+lazy_static! {
+ static ref TARGET_GUILD: u64 = dotenv!("TARGET_GUILD").parse().expect("unable to parse TARGET_GUILD as u64");
+ static ref TARGET_GUILD_ID: GuildId = GuildId(*TARGET_GUILD);
+}
+
+
+struct Handler;
+impl EventHandler for Handler {
+ fn ready(&self, _: Context, r: Ready) {
+ let guild = r.guilds.iter()
+ .find(|g| g.id().0 == *TARGET_GUILD);
+
+ if guild.is_none() {
+ info!("bot isn't in configured guild. join here: {:?}", OAUTH_URL.as_str());
+ }
+ }
+}
+
+fn run() -> Result<()> {
+ let token = &env::var("THULANI_TOKEN")?;
+ let mut client = Client::new(token, Handler)?;
+
+ commands::VoiceManager::register(&mut client);
+ commands::PlayQueue::register(&mut client);
+
+ let owner_id = must_env_lookup::<u64>("OWNER_ID");
+ let mut framework = StandardFramework::new()
+ .configure(|c| c
+ .allow_dm(false)
+ .allow_whitespace(true)
+ .prefixes(vec!["!thulani ", "!thulan ", "!thulando madando ", "!thulando "])
+ .ignore_bots(true)
+ .on_mention(false)
+ .owners(vec![UserId(owner_id)].into_iter().collect())
+ .case_insensitivity(true)
+ )
+ .before(|_ctx, message, cmd| {
+ let result = message.guild_id().map_or(false, |x| x.0 == *TARGET_GUILD);
+ debug!("got command '{}' from user '{}' ({}). accept: {}", cmd, message.author.name, message.author.id, result);
+
+ result
+ })
+ .after(|_ctx, _msg, _cmd, err| {
+ match err {
+ Ok(()) => {
+ trace!("command completed successfully");
+ },
+ Err(e) => {
+ error!("encountered error: {:?}", e);
+ }
+ }
+ })
+ .bucket("Standard", 1, 10, 3)
+ .customised_help(help_commands::with_embeds, |c| {
+ c
+ });
+
+ framework = register_commands(framework);
+ client.with_framework(framework);
+
+ let shard_manager = client.shard_manager.clone();
+ ctrlc::set_handler(move || {
+ info!("shutting down");
+ shard_manager.lock().shutdown_all();
+ }).expect("unable to create SIGINT/SIGTERM handlers");
+
+ client.start()?;
+
+ Ok(())
+}
+
+fn main() {
+ const BACKOFF_FACTOR: f64 = 2.0;
+ const MAX_BACKOFFS: usize = 3;
+ const BACKOFF_INIT: f64 = 100.0;
+
+ const MIN_RUN_DURATION: Duration = Duration::from_secs(120);
+
+ dotenv().ok();
+
+ use fern::colors::{Color, ColoredLevelConfig};
+ let colors = ColoredLevelConfig::new()
+ .info(Color::Green)
+ .debug(Color::BrightBlue)
+ .trace(Color::BrightMagenta);
+
+ fern::Dispatch::new()
+ .level_for("serenity::voice::connection", log::LevelFilter::Error)
+ .chain(fern::Dispatch::new()
+ .format(move |out, message, record| {
+ out.finish(format_args!(
+ "{} [{}] [{}] {}",
+ chrono::Local::now().format("%_m/%_d/%y %l:%M:%S%P"),
+ colors.color(record.level()),
+ record.target(),
+ message
+ ))
+ })
+ .level(log::LevelFilter::Warn)
+ .level_for("thulani", log::LevelFilter::Debug)
+ .chain(std::io::stdout())
+ )
+ .chain(fern::Dispatch::new()
+ .format(|out, message, record| {
+ out.finish(format_args!(
+ "{} [{}] [{}] {}",
+ chrono::Local::now().format("%_m/%_d/%y %l:%M:%S%P"),
+ record.level(),
+ record.target(),
+ message
+ ))
+ })
+
+ .level(log::LevelFilter::Info)
+ .level_for("thulani", log::LevelFilter::Trace)
+ .chain(fern::log_file("thulani.log").expect("problem creating log file"))
+ )
+ .apply()
+ .expect("error initializing logging");
+
+ let mut backoff_count: usize = 0;
+
+ loop {
+ let start = Instant::now();
+
+ info!("starting bot");
+ match run() {
+ Err(e) => {
+ error!("error encountered running client: {}", e);
+ e.iter().skip(1).for_each(|e| {
+ error!("caused by: {}", e);
+ });
+
+ if let Some(bt) = e.backtrace() {
+ error!("backtrace: {:?}", bt);
+ }
+ },
+ _ => {
+ // NOTE: we MUST have gotten here through SIGINT/SIGTERM handlers
+ ::std::process::exit(0);
+ }
+ }
+
+ if Instant::now() - start >= MIN_RUN_DURATION {
+ backoff_count = 0;
+ continue;
+ }
+
+ backoff_count += 1;
+ if backoff_count >= MAX_BACKOFFS {
+ panic!("restarted bot too many times");
+ }
+
+ let backoff_millis = (BACKOFF_INIT * BACKOFF_FACTOR.powi(backoff_count as i32)) as u64;
+ info!("bot died too quickly. backing off, retrying in {}ms.", backoff_millis);
+
+ thread::sleep(Duration::from_millis(backoff_millis));
+ }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..1eb4b23
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,34 @@
+use std::env;
+use std::str::FromStr;
+
+use serenity::model::permissions::Permissions;
+use url::Url;
+
+lazy_static! {
+ static ref REQUIRED_PERMS: Permissions = Permissions::EMBED_LINKS |
+ Permissions::READ_MESSAGES |
+ Permissions::ADD_REACTIONS |
+ Permissions::SEND_MESSAGES |
+ Permissions::SEND_TTS_MESSAGES |
+ Permissions::MENTION_EVERYONE |
+ Permissions::USE_EXTERNAL_EMOJIS |
+ Permissions::CONNECT |
+ Permissions::SPEAK |
+ Permissions::CHANGE_NICKNAME |
+ Permissions::USE_VAD |
+ Permissions::ATTACH_FILES;
+}
+
+lazy_static! {
+ pub static ref OAUTH_URL: Url = Url::parse(
+ &format!(
+ "https://discordapp.com/api/oauth2/authorize?scope=bot&permissions={}&client_id={}",
+ REQUIRED_PERMS.bits(), dotenv!("THULANI_CLIENT_ID"),
+ )
+ ).unwrap();
+}
+
+pub fn must_env_lookup<T: FromStr>(s: &str) -> T {
+ env::var(s).expect(&format!("missing env var {}", s))
+ .parse::<T>().unwrap_or_else(|_| panic!(format!("bad format for {}", s)))
+}
diff --git a/thulani.go b/thulani.go
deleted file mode 100644
index 1acde39..0000000
--- a/thulani.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package thulani
-
-import (
- "net/url"
- "os"
- "os/signal"
- "regexp"
- "strings"
- "syscall"
-
- "github.com/bwmarrin/discordgo"
- "github.com/mammothbane/thulani-go/downloader"
-)
-
-var config *Config
-var regex *regexp.Regexp
-var manager *downloader.DownloadManager
-
-func Run(conf *Config) {
- //defer profile.Start(profile.ProfilePath("."), profile.BlockProfile).Stop()
-
- config = conf
- regex = regexp.MustCompile(`(?i)^[!/]` + conf.Trigger + " (.*)")
-
- dg, err := discordgo.New("Bot " + conf.Token)
- handle(err)
-
- dg.AddHandler(onReady)
- dg.AddHandler(onMessage)
- dg.AddHandler(onGuildCreate)
- dg.Open()
-
- sc := make(chan os.Signal, 1)
- signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
- <-sc
-
- dg.Close()
-}
-
-func onReady(s *discordgo.Session, m *discordgo.Ready) {
- log.Infof("Logged in as %v (%v)", m.User.Username, m.User.ID)
-
- s.UpdateStatus(0, "literally nothing")
-
- joined := false
- for _, v := range m.Guilds {
- if v.ID != config.GuildStr() {
- joined = true
- break
- }
- }
-
- if !joined {
- log.Warningf("Server in config not available! Click here to enable thulani on your server: %v", oauthUrl())
- }
- manager = downloader.NewManager(s, config.GuildStr(), config.VoiceChannelStr())
-}
-
-func onGuildCreate(s *discordgo.Session, m *discordgo.GuildCreate) {
- member, err := s.GuildMember(m.Guild.ID, s.State.User.ID)
- if err != nil {
- log.Warningf("joined guild %v but was unable to get member id: %q", m.Name, err)
- log.Notice("please reconnect to guild: %v", oauthUrl())
- s.GuildLeave(m.Guild.ID)
- return
- }
- log.Infof("joined guild %v", m.Name)
-
- perms := 0
-
- //log.Debugf("listing roles for %v", m.Name)
- for _, role := range m.Roles {
- //log.Debugf("%q (%v)", role.Name, role.ID)
- for _, mRole := range member.Roles {
- if role.ID == mRole {
- perms |= role.Permissions
-
- log.Infof("discovered own role: %v (%v)", role.Name, role.ID)
- }
- }
- }
-
- if perms&requestedPerms != requestedPerms {
- log.Errorf("server didn't grant us the desired permissions.")
- s.GuildLeave(m.Guild.ID)
- log.Warningf("Don't disable any permissions or thulani will be a little sponge man! Click here to die: %v", oauthUrl())
- return
- }
-
- err = s.GuildMemberNickname(m.Guild.ID, "@me", "newlani")
- if err != nil {
- log.Warningf("unable to update nickname: %q", err)
- }
-}
-
-func onMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
- if m.Author.ID == s.State.User.ID {
- return
- }
-
- log.Debugf("got message %q", m.Content)
-
- ctx, err := newCtx(s, m)
- if err != nil {
- log.Errorf("error constructing message context: %q", err)
- return
- }
-
- for _, v := range extraMemes {
- v(ctx)
- }
-
- if !ctx.Matched {
- log.Infof("Message didn't match. Ignoring.")
- return
- }
-
- if ctx.Channel.IsPrivate {
- log.Infof("Ignoring private message")
- return
- }
-
- if ctx.Guild.ID != config.GuildStr() {
- log.Infof("Wrong guild. Ignoring.")
- return
- }
-
- fn, ok := cmdMap[strings.ToLower(ctx.Command)]
- if ok {
- log.Debugf("message matched a known command: %q", strings.ToLower(ctx.Command))
-
- authorized := ctx.Author.ID == config.AdminStr()
-
- if !authorized {
- authorMember, err := ctx.GuildMember(ctx.Guild.ID, ctx.Author.ID)
- if err != nil {
- log.Errorf("unable to get guild member for id %q", ctx.Author.Username)
- ctx.sendMessage("who the fuck are you?", true)
- return
- }
-
- for _, v := range authorMember.Roles {
- if v == config.OpRoleStr() {
- authorized = true
- }
- }
- }
-
- if !authorized {
- log.Infof("User %v not authorized.", m.Author.Username)
- ctx.sendMessage("fuck you. you're not allowed to do that.", m.Tts)
- return
- }
-
- log.Debugf("user was authorized for %q. executing.", strings.ToLower(ctx.Command))
- fn(ctx)
- return
- }
-
- // it's not a command we know; we're looking for a url
- target, err := url.Parse(ctx.Command)
- if err != nil {
- log.Errorf("Url parse failed: %q", err)
- ctx.sendMessage("format your commands right. fuck you.", m.Tts)
- return
- }
-
- if target.Path == "" || (target.Path == "/watch" && len(target.Query()) == 0) {
- log.Warningf("Bad url format: %q", ctx.Command)
- ctx.sendMessage("that\nis\na\nbad\nurl", m.Tts)
- return
- }
-
- if strings.Contains(target.Hostname(), "imgur") {
- log.Infof("Ignoring imgur link.")
-
- if m.Author.Username == "boomshticky" {
- ctx.sendMessage("fuck you conway", true)
- } else {
- ctx.sendMessage("NO IMGUR", m.Tts)
- }
- }
-
- if err := manager.Enqueue(target.String(), 0, 0); err != nil {
- log.Errorf("unable to enqueue video: %q", err)
- ctx.sendMessage("you fucked up the video.", ctx.Tts)
- return
- }
- log.Infof("started playing from: %q", target.String())
-}
diff --git a/util.go b/util.go
deleted file mode 100644
index b4a806d..0000000
--- a/util.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package thulani
-
-import (
- "encoding/json"
- "net/url"
- "os"
- "strconv"
- "sync"
-
- "github.com/bwmarrin/discordgo"
- "github.com/op/go-logging"
-)
-
-const help = `wew lad. you should know these commands already.
-
-Usage: ` + "`!thulani [command]`" + `
-
-commands:
-**help**: print this help message
-**[url]**: a url with media that thulani can play. queued up to play after everything that's already waiting.
-**list, queue**: list items in the queue, as well as the currently-playing item.
-**pause**: pause sound.
-**resume**: resume sound.
-**die**: empty the queue and stop playing.
-**skip**: skip the current item.
-`
-
-var log = logging.MustGetLogger("thulani")
-
-type Config struct {
- Trigger string `json:"trigger"`
- QueueSize uint `json:"queue_size"`
- AdminID uint `json:"admin_id"`
- OpRoleID uint `json:"op_role_id"`
- GuildID uint `json:"guild_id"`
- VoiceChannelID uint `json:"voice_channel_id"`
- Token string `json:"token"`
- ClientID string `json:"client_id"`
- ClientSecret string `json:"client_secret"`
-}
-
-func (c *Config) GuildStr() string {
- return strconv.Itoa(int(c.GuildID))
-}
-
-func (c *Config) VoiceChannelStr() string {
- return strconv.Itoa(int(c.VoiceChannelID))
-}
-
-func (c *Config) AdminStr() string {
- return strconv.Itoa(int(c.AdminID))
-}
-
-func (c *Config) OpRoleStr() string {
- return strconv.Itoa(int(c.OpRoleID))
-}
-
-func handle(err error) {
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func LoadConfig(filename string) (*Config, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
-
- var conf Config
- err = json.NewDecoder(file).Decode(&conf)
- return &conf, err
-}
-
-const requestedPerms = discordgo.PermissionEmbedLinks |
- discordgo.PermissionReadMessages |
- discordgo.PermissionAddReactions |
- discordgo.PermissionSendMessages |
- discordgo.PermissionSendTTSMessages |
- discordgo.PermissionMentionEveryone |
- discordgo.PermissionUseExternalEmojis |
- discordgo.PermissionVoiceConnect |
- discordgo.PermissionVoiceSpeak |
- discordgo.PermissionChangeNickname |
- discordgo.PermissionVoiceUseVAD |
- discordgo.PermissionAttachFiles
-
-var _oauthUrl string
-var oauthOnce sync.Once
-
-func oauthUrl() string {
- oauthOnce.Do(func() {
- oUrl, err := url.Parse("https://discordapp.com/api/oauth2/authorize")
- if err != nil {
- panic(err)
- }
-
- q := oUrl.Query()
- q.Add("scope", "bot")
- q.Add("permissions", strconv.Itoa(requestedPerms))
- q.Add("client_id", config.ClientID)
- oUrl.RawQuery = q.Encode()
-
- _oauthUrl = oUrl.String()
- })
-
- return _oauthUrl
-}
diff --git a/wav/dr_wav.h b/wav/dr_wav.h
deleted file mode 100644
index 6aea4a3..0000000
--- a/wav/dr_wav.h
+++ /dev/null
@@ -1,1960 +0,0 @@
-// WAV audio loader. Public domain. See "unlicense" statement at the end of this file.
-// dr_wav - v0.5g - 2017-06-16
-//
-// David Reid - mackron@gmail.com
-
-// USAGE
-//
-// This is a single-file library. To use it, do something like the following in one .c file.
-// #define DR_WAV_IMPLEMENTATION
-// #include "dr_wav.h"
-//
-// You can then #include this file in other parts of the program as you would with any other header file. Do something
-// like the following to read audio data:
-//
-// drwav wav;
-// if (!drwav_init_file(&wav, "my_song.wav")) {
-// // Error opening WAV file.
-// }
-//
-// dr_int32* pDecodedInterleavedSamples = malloc(wav.totalSampleCount * sizeof(dr_int32));
-// size_t numberOfSamplesActuallyDecoded = drwav_read_s32(&wav, wav.totalSampleCount, pDecodedInterleavedSamples);
-//
-// ...
-//
-// drwav_uninit(&wav);
-//
-// You can also use drwav_open() to allocate and initialize the loader for you:
-//
-// drwav* pWav = drwav_open_file("my_song.wav");
-// if (pWav == NULL) {
-// // Error opening WAV file.
-// }
-//
-// ...
-//
-// drwav_close(pWav);
-//
-// If you just want to quickly open and read the audio data in a single operation you can do something like this:
-//
-// unsigned int channels;
-// unsigned int sampleRate;
-// dr_uint64 totalSampleCount;
-// float* pSampleData = drwav_open_and_read_file_s32("my_song.wav", &channels, &sampleRate, &totalSampleCount);
-// if (pSampleData == NULL) {
-// // Error opening and reading WAV file.
-// }
-//
-// ...
-//
-// drwav_free(pSampleData);
-//
-// The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in
-// this case), but you can still output the audio data in it's internal format (see notes below for supported formats):
-//
-// size_t samplesRead = drwav_read(&wav, wav.totalSampleCount, pDecodedInterleavedSamples);
-//
-// You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for
-// a particular data format:
-//
-// size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer);
-//
-//
-// dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work
-// without any manual intervention.
-//
-//
-//
-// OPTIONS
-// #define these options before including this file.
-//
-// #define DR_WAV_NO_CONVERSION_API
-// Disables conversion APIs such as drwav_read_f32() and drwav_s16_to_f32().
-//
-// #define DR_WAV_NO_STDIO
-// Disables drwav_open_file().
-//
-//
-//
-// QUICK NOTES
-// - Samples are always interleaved.
-// - The default read function does not do any data conversion. Use drwav_read_f32() to read and convert audio data
-// to IEEE 32-bit floating point samples. Likewise, use drwav_read_s32() to read and convert auto data to signed
-// 32-bit PCM. Tested and supported internal formats include the following:
-// - Unsigned 8-bit PCM
-// - Signed 12-bit PCM
-// - Signed 16-bit PCM
-// - Signed 24-bit PCM
-// - Signed 32-bit PCM
-// - IEEE 32-bit floating point.
-// - IEEE 64-bit floating point.
-// - A-law and u-law
-// - Microsoft ADPCM is not currently supported.
-// - dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format.
-
-
-#ifndef dr_wav_h
-#define dr_wav_h
-
-#include <stddef.h>
-
-#ifndef DR_SIZED_TYPES_DEFINED
-#define DR_SIZED_TYPES_DEFINED
-#if defined(_MSC_VER) && _MSC_VER < 1600
-typedef signed char dr_int8;
-typedef unsigned char dr_uint8;
-typedef signed short dr_int16;
-typedef unsigned short dr_uint16;
-typedef signed int dr_int32;
-typedef unsigned int dr_uint32;
-typedef signed __int64 dr_int64;
-typedef unsigned __int64 dr_uint64;
-#else
-#include <stdint.h>
-typedef int8_t dr_int8;
-typedef uint8_t dr_uint8;
-typedef int16_t dr_int16;
-typedef uint16_t dr_uint16;
-typedef int32_t dr_int32;
-typedef uint32_t dr_uint32;
-typedef int64_t dr_int64;
-typedef uint64_t dr_uint64;
-#endif
-typedef dr_uint8 dr_bool8;
-typedef dr_uint32 dr_bool32;
-#define DR_TRUE 1
-#define DR_FALSE 0
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Common data formats.
-#define DR_WAVE_FORMAT_PCM 0x1
-#define DR_WAVE_FORMAT_ADPCM 0x2 // Not currently supported.
-#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3
-#define DR_WAVE_FORMAT_ALAW 0x6
-#define DR_WAVE_FORMAT_MULAW 0x7
-#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE
-
-typedef enum
-{
- drwav_seek_origin_start,
- drwav_seek_origin_current
-} drwav_seek_origin;
-
-typedef enum
-{
- drwav_container_riff,
- drwav_container_w64
-} drwav_container;
-
-// Callback for when data is read. Return value is the number of bytes actually read.
-typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead);
-
-// Callback for when data needs to be seeked. Return value is true on success; false on failure.
-typedef dr_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin);
-
-// Structure for internal use. Only used for loaders opened with drwav_open_memory.
-typedef struct
-{
- const unsigned char* data;
- size_t dataSize;
- size_t currentReadPos;
-} drwav__memory_stream;
-
-typedef struct
-{
- // The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications
- // that require support for data formats not natively supported by dr_wav.
- unsigned short formatTag;
-
- // The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc.
- unsigned short channels;
-
- // The sample rate. Usually set to something like 44100.
- unsigned int sampleRate;
-
- // Average bytes per second. You probably don't need this, but it's left here for informational purposes.
- unsigned int avgBytesPerSec;
-
- // Block align. This is equal to the number of channels * bytes per sample.
- unsigned short blockAlign;
-
- // Bit's per sample.
- unsigned short bitsPerSample;
-
- // The size of the extended data. Only used internally for validation, but left here for informational purposes.
- unsigned short extendedSize;
-
- // The number of valid bits per sample. When <formatTag> is equal to WAVE_FORMAT_EXTENSIBLE, <bitsPerSample>
- // is always rounded up to the nearest multiple of 8. This variable contains information about exactly how
- // many bits a valid per sample. Mainly used for informational purposes.
- unsigned short validBitsPerSample;
-
- // The channel mask. Not used at the moment.
- unsigned int channelMask;
-
- // The sub-format, exactly as specified by the wave file.
- unsigned char subFormat[16];
-} drwav_fmt;
-
-typedef struct
-{
- // A pointer to the function to call when more data is needed.
- drwav_read_proc onRead;
-
- // A pointer to the function to call when the wav file needs to be seeked.
- drwav_seek_proc onSeek;
-
- // The user data to pass to callbacks.
- void* pUserData;
-
-
- // Whether or not the WAV file is formatted as a standard RIFF file or W64.
- drwav_container container;
-
-
- // Structure containing format information exactly as specified by the wav file.
- drwav_fmt fmt;
-
- // The sample rate. Will be set to something like 44100.
- unsigned int sampleRate;
-
- // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc.
- unsigned short channels;
-
- // The bits per sample. Will be set to somthing like 16, 24, etc.
- unsigned short bitsPerSample;
-
- // The number of bytes per sample.
- unsigned short bytesPerSample;
-
- // Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE).
- unsigned short translatedFormatTag;
-
- // The total number of samples making up the audio data. Use <totalSampleCount> * <bytesPerSample> to calculate
- // the required size of a buffer to hold the entire audio data.
- dr_uint64 totalSampleCount;
-
-
- // The number of bytes remaining in the data chunk.
- dr_uint64 bytesRemaining;
-
-
- // A hack to avoid a malloc() when opening a decoder with drwav_open_memory().
- drwav__memory_stream memoryStream;
-} drwav;
-
-
-// Initializes a pre-allocated drwav object.
-//
-// Returns true if successful; false otherwise.
-dr_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData);
-
-// Uninitializes the given drwav object. Use this only for objects initialized with drwav_init().
-void drwav_uninit(drwav* pWav);
-
-
-// Opens a wav file using the given callbacks.
-//
-// Returns null on error. Close the loader with drwav_close().
-//
-// This is different from drwav_init() in that it will allocate the drwav object for you via malloc() before
-// initializing it.
-drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData);
-
-// Uninitializes and deletes the the given drwav object. Use this only for objects created with drwav_open().
-void drwav_close(drwav* pWav);
-
-
-// Reads raw audio data.
-//
-// This is the lowest level function for reading audio data. It simply reads the given number of
-// bytes of the raw internal sample data.
-//
-// Returns the number of bytes actually read.
-size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut);
-
-// Reads a chunk of audio data in the native internal format.
-//
-// This is typically the most efficient way to retrieve audio data, but it does not do any format
-// conversions which means you'll need to convert the data manually if required.
-//
-// If the return value is less than <samplesToRead> it means the end of the file has been reached or
-// you have requested more samples than can possibly fit in the output buffer.
-//
-// This function will only work when sample data is of a fixed size. If you are using an unusual
-// format which uses variable sized samples, consider using drwav_read_raw(), but don't combine them.
-dr_uint64 drwav_read(drwav* pWav, dr_uint64 samplesToRead, void* pBufferOut);
-
-// Seeks to the given sample.
-//
-// The return value is DR_FALSE if an error occurs, DR_TRUE if successful.
-dr_bool32 drwav_seek_to_sample(drwav* pWav, dr_uint64 sample);
-
-
-
-//// Convertion Utilities ////
-#ifndef DR_WAV_NO_CONVERSION_API
-
-// Reads a chunk of audio data and converts it to signed 16-bit PCM samples.
-//
-// Returns the number of samples actually read.
-//
-// If the return value is less than <samplesToRead> it means the end of the file has been reached.
-dr_uint64 drwav_read_s16(drwav* pWav, dr_uint64 samplesToRead, dr_int16* pBufferOut);
-
-
-// Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples.
-//
-// Returns the number of samples actually read.
-//
-// If the return value is less than <samplesToRead> it means the end of the file has been reached.
-dr_uint64 drwav_read_f32(drwav* pWav, dr_uint64 samplesToRead, float* pBufferOut);
-
-// Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples.
-void drwav_u8_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples.
-void drwav_s16_to_f32(float* pOut, const dr_int16* pIn, size_t sampleCount);
-
-// Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples.
-void drwav_s24_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples.
-void drwav_s32_to_f32(float* pOut, const dr_int32* pIn, size_t sampleCount);
-
-// Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples.
-void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount);
-
-// Low-level function for converting A-law samples to IEEE 32-bit floating point samples.
-void drwav_alaw_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting u-law samples to IEEE 32-bit floating point samples.
-void drwav_ulaw_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-
-// Reads a chunk of audio data and converts it to signed 32-bit PCM samples.
-//
-// Returns the number of samples actually read.
-//
-// If the return value is less than <samplesToRead> it means the end of the file has been reached.
-dr_uint64 drwav_read_s32(drwav* pWav, dr_uint64 samplesToRead, dr_int32* pBufferOut);
-
-// Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples.
-void drwav_u8_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples.
-void drwav_s16_to_s32(dr_int32* pOut, const dr_int16* pIn, size_t sampleCount);
-
-// Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples.
-void drwav_s24_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples.
-void drwav_f32_to_s32(dr_int32* pOut, const float* pIn, size_t sampleCount);
-
-// Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples.
-void drwav_f64_to_s32(dr_int32* pOut, const double* pIn, size_t sampleCount);
-
-// Low-level function for converting A-law samples to signed 32-bit PCM samples.
-void drwav_alaw_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-// Low-level function for converting u-law samples to signed 32-bit PCM samples.
-void drwav_ulaw_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount);
-
-#endif //DR_WAV_NO_CONVERSION_API
-
-
-//// High-Level Convenience Helpers ////
-
-#ifndef DR_WAV_NO_STDIO
-
-// Helper for initializing a wave file using stdio.
-//
-// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav
-// objects because the operating system may restrict the number of file handles an application can have open at
-// any given time.
-dr_bool32 drwav_init_file(drwav* pWav, const char* filename);
-
-// Helper for opening a wave file using stdio.
-//
-// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav
-// objects because the operating system may restrict the number of file handles an application can have open at
-// any given time.
-drwav* drwav_open_file(const char* filename);
-
-#endif //DR_WAV_NO_STDIO
-
-// Helper for initializing a file from a pre-allocated memory buffer.
-//
-// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for
-// the lifetime of the drwav object.
-//
-// The buffer should contain the contents of the entire wave file, not just the sample data.
-dr_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize);
-
-// Helper for opening a file from a pre-allocated memory buffer.
-//
-// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for
-// the lifetime of the drwav object.
-//
-// The buffer should contain the contents of the entire wave file, not just the sample data.
-drwav* drwav_open_memory(const void* data, size_t dataSize);
-
-
-
-#ifndef DR_WAV_NO_CONVERSION_API
-// Opens and reads a wav file in a single operation.
-dr_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-dr_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-#ifndef DR_WAV_NO_STDIO
-// Opens an decodes a wav file in a single operation.
-dr_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-dr_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-#endif
-
-// Opens an decodes a wav file from a block of memory in a single operation.
-dr_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-dr_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount);
-#endif
-
-// Frees data that was allocated internally by dr_wav.
-void drwav_free(void* pDataReturnedByOpenAndRead);
-
-#ifdef __cplusplus
-}
-#endif
-#endif // dr_wav_h
-
-
-/////////////////////////////////////////////////////
-//
-// IMPLEMENTATION
-//
-/////////////////////////////////////////////////////
-
-#ifdef DR_WAV_IMPLEMENTATION
-#include <stdlib.h>
-#include <string.h> // For memcpy()
-#include <limits.h>
-#include <assert.h>
-
-#ifndef DR_WAV_NO_STDIO
-#include <stdio.h>
-#endif
-
-static const dr_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; // 66666972-912E-11CF-A5D6-28DB04C10000
-static const dr_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 65766177-ACF3-11D3-8CD1-00C04F8EDB8A
-static const dr_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A
-static const dr_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A
-static const dr_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 61746164-ACF3-11D3-8CD1-00C04F8EDB8A
-
-static dr_bool32 drwav__guid_equal(const dr_uint8 a[16], const dr_uint8 b[16])
-{
- const dr_uint32* a32 = (const dr_uint32*)a;
- const dr_uint32* b32 = (const dr_uint32*)b;
-
- return
- a32[0] == b32[0] &&
- a32[1] == b32[1] &&
- a32[2] == b32[2] &&
- a32[3] == b32[3];
-}
-
-static dr_bool32 drwav__fourcc_equal(const unsigned char* a, const char* b)
-{
- return
- a[0] == b[0] &&
- a[1] == b[1] &&
- a[2] == b[2] &&
- a[3] == b[3];
-}
-
-
-
-
-static int drwav__is_little_endian()
-{
- int n = 1;
- return (*(char*)&n) == 1;
-}
-
-static unsigned short drwav__bytes_to_u16(const unsigned char* data)
-{
- if (drwav__is_little_endian()) {
- return (data[0] << 0) | (data[1] << 8);
- } else {
- return (data[1] << 0) | (data[0] << 8);
- }
-}
-
-static unsigned int drwav__bytes_to_u32(const unsigned char* data)
-{
- if (drwav__is_little_endian()) {
- return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
- } else {
- return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24);
- }
-}
-
-static dr_uint64 drwav__bytes_to_u64(const unsigned char* data)
-{
- if (drwav__is_little_endian()) {
- return
- ((dr_uint64)data[0] << 0ULL) | ((dr_uint64)data[1] << 8ULL) | ((dr_uint64)data[2] << 16ULL) | ((dr_uint64)data[3] << 24ULL) |
- ((dr_uint64)data[4] << 32ULL) | ((dr_uint64)data[5] << 40ULL) | ((dr_uint64)data[6] << 48ULL) | ((dr_uint64)data[7] << 56ULL);
- } else {
- return
- ((dr_uint64)data[7] << 0ULL) | ((dr_uint64)data[6] << 8ULL) | ((dr_uint64)data[5] << 16ULL) | ((dr_uint64)data[4] << 24ULL) |
- ((dr_uint64)data[3] << 32ULL) | ((dr_uint64)data[2] << 40ULL) | ((dr_uint64)data[1] << 48ULL) | ((dr_uint64)data[0] << 56ULL);
- }
-}
-
-static void drwav__bytes_to_guid(const unsigned char* data, dr_uint8* guid)
-{
- for (int i = 0; i < 16; ++i) {
- guid[i] = data[i];
- }
-}
-
-
-typedef struct
-{
- union
- {
- dr_uint8 fourcc[4];
- dr_uint8 guid[16];
- } id;
-
- // The size in bytes of the chunk.
- dr_uint64 sizeInBytes;
-
- // RIFF = 2 byte alignment.
- // W64 = 8 byte alignment.
- unsigned int paddingSize;
-
-} drwav__chunk_header;
-
-static dr_bool32 drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav__chunk_header* pHeaderOut)
-{
- if (container == drwav_container_riff) {
- if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) {
- return DR_FALSE;
- }
-
- unsigned char sizeInBytes[4];
- if (onRead(pUserData, sizeInBytes, 4) != 4) {
- return DR_FALSE;
- }
-
- pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes);
- pHeaderOut->paddingSize = pHeaderOut->sizeInBytes % 2;
- } else {
- if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) {
- return DR_FALSE;
- }
-
- unsigned char sizeInBytes[8];
- if (onRead(pUserData, sizeInBytes, 8) != 8) {
- return DR_FALSE;
- }
-
- pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; // <-- Subtract 24 because w64 includes the size of the header.
- pHeaderOut->paddingSize = pHeaderOut->sizeInBytes % 8;
- }
-
- return DR_TRUE;
-}
-
-static dr_bool32 drwav__seek_forward(drwav_seek_proc onSeek, dr_uint64 offset, void* pUserData)
-{
- dr_uint64 bytesRemainingToSeek = offset;
- while (bytesRemainingToSeek > 0) {
- if (bytesRemainingToSeek > 0x7FFFFFFF) {
- if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) {
- return DR_FALSE;
- }
- bytesRemainingToSeek -= 0x7FFFFFFF;
- } else {
- if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) {
- return DR_FALSE;
- }
- bytesRemainingToSeek = 0;
- }
- }
-
- return DR_TRUE;
-}
-
-
-static dr_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_fmt* fmtOut)
-{
- drwav__chunk_header header;
- if (!drwav__read_chunk_header(onRead, pUserData, container, &header)) {
- return DR_FALSE;
- }
-
-
- // Skip junk chunks.
- if ((container == drwav_container_riff && drwav__fourcc_equal(header.id.fourcc, "JUNK")) || (container == drwav_container_w64 && drwav__guid_equal(header.id.guid, drwavGUID_W64_JUNK))) {
- if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) {
- return DR_FALSE;
- }
-
- return drwav__read_fmt(onRead, onSeek, pUserData, container, fmtOut);
- }
-
-
- // Validation.
- if (container == drwav_container_riff) {
- if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) {
- return DR_FALSE;
- }
- } else {
- if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) {
- return DR_FALSE;
- }
- }
-
-
- unsigned char fmt[16];
- if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) {
- return DR_FALSE;
- }
-
- fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0);
- fmtOut->channels = drwav__bytes_to_u16(fmt + 2);
- fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4);
- fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8);
- fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12);
- fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14);
-
- fmtOut->extendedSize = 0;
- fmtOut->validBitsPerSample = 0;
- fmtOut->channelMask = 0;
- memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat));
-
- if (header.sizeInBytes > 16) {
- unsigned char fmt_cbSize[2];
- if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) {
- return DR_FALSE; // Expecting more data.
- }
-
- int bytesReadSoFar = 18;
-
- fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize);
- if (fmtOut->extendedSize > 0) {
- if (fmtOut->extendedSize != 22) {
- return DR_FALSE; // The extended size should be equal to 22.
- }
-
- unsigned char fmtext[22];
- if (onRead(pUserData, fmtext, sizeof(fmtext)) != sizeof(fmtext)) {
- return DR_FALSE; // Expecting more data.
- }
-
- fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0);
- fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2);
- drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat);
-
- bytesReadSoFar += 22;
- }
-
- // Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size.
- if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) {
- return DR_FALSE;
- }
- }
-
- if (header.paddingSize > 0) {
- if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) {
- return DR_FALSE;
- }
- }
-
- return DR_TRUE;
-}
-
-
-#ifndef DR_WAV_NO_STDIO
-static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead)
-{
- return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData);
-}
-
-static dr_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin)
-{
- return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0;
-}
-
-dr_bool32 drwav_init_file(drwav* pWav, const char* filename)
-{
- FILE* pFile;
-#ifdef _MSC_VER
- if (fopen_s(&pFile, filename, "rb") != 0) {
- return DR_FALSE;
- }
-#else
- pFile = fopen(filename, "rb");
- if (pFile == NULL) {
- return DR_FALSE;
- }
-#endif
-
- return drwav_init(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile);
-}
-
-drwav* drwav_open_file(const char* filename)
-{
- FILE* pFile;
-#ifdef _MSC_VER
- if (fopen_s(&pFile, filename, "rb") != 0) {
- return NULL;
- }
-#else
- pFile = fopen(filename, "rb");
- if (pFile == NULL) {
- return NULL;
- }
-#endif
-
- drwav* pWav = drwav_open(drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile);
- if (pWav == NULL) {
- fclose(pFile);
- return NULL;
- }
-
- return pWav;
-}
-#endif //DR_WAV_NO_STDIO
-
-
-static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead)
-{
- drwav__memory_stream* memory = (drwav__memory_stream*)pUserData;
- assert(memory != NULL);
- assert(memory->dataSize >= memory->currentReadPos);
-
- size_t bytesRemaining = memory->dataSize - memory->currentReadPos;
- if (bytesToRead > bytesRemaining) {
- bytesToRead = bytesRemaining;
- }
-
- if (bytesToRead > 0) {
- memcpy(pBufferOut, memory->data + memory->currentReadPos, bytesToRead);
- memory->currentReadPos += bytesToRead;
- }
-
- return bytesToRead;
-}
-
-static dr_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin)
-{
- drwav__memory_stream* memory = (drwav__memory_stream*)pUserData;
- assert(memory != NULL);
-
- if (origin == drwav_seek_origin_current) {
- if (offset > 0) {
- if (memory->currentReadPos + offset > memory->dataSize) {
- offset = (int)(memory->dataSize - memory->currentReadPos); // Trying to seek too far forward.
- }
- } else {
- if (memory->currentReadPos < (size_t)-offset) {
- offset = -(int)memory->currentReadPos; // Trying to seek too far backwards.
- }
- }
- } else {
- if ((dr_uint32)offset <= memory->dataSize) {
- memory->currentReadPos = offset;
- } else {
- memory->currentReadPos = memory->dataSize; // Trying to seek too far forward.
- }
- }
-
- // This will never underflow thanks to the clamps above.
- memory->currentReadPos += offset;
- return DR_TRUE;
-}
-
-dr_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize)
-{
- drwav__memory_stream memoryStream;
- memoryStream.data = (const unsigned char*)data;
- memoryStream.dataSize = dataSize;
- memoryStream.currentReadPos = 0;
-
- if (!drwav_init(pWav, drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream)) {
- return DR_FALSE;
- }
-
- pWav->memoryStream = memoryStream;
- pWav->pUserData = &pWav->memoryStream;
- return DR_TRUE;
-}
-
-drwav* drwav_open_memory(const void* data, size_t dataSize)
-{
- drwav__memory_stream memoryStream;
- memoryStream.data = (const unsigned char*)data;
- memoryStream.dataSize = dataSize;
- memoryStream.currentReadPos = 0;
-
- drwav* pWav = drwav_open(drwav__on_read_memory, drwav__on_seek_memory, (void*)&memoryStream);
- if (pWav == NULL) {
- return NULL;
- }
-
- pWav->memoryStream = memoryStream;
- pWav->pUserData = &pWav->memoryStream;
- return pWav;
-}
-
-
-dr_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData)
-{
- if (onRead == NULL || onSeek == NULL) {
- return DR_FALSE;
- }
-
-
- // The first 4 bytes should be the RIFF identifier.
- unsigned char riff[4];
- if (onRead(pUserData, riff, sizeof(riff)) != sizeof(riff)) {
- return DR_FALSE; // Failed to read data.
- }
-
- // The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for
- // w64 it will start with "riff".
- if (drwav__fourcc_equal(riff, "RIFF")) {
- pWav->container = drwav_container_riff;
- } else if (drwav__fourcc_equal(riff, "riff")) {
- pWav->container = drwav_container_w64;
-
- // Check the rest of the GUID for validity.
- dr_uint8 riff2[12];
- if (onRead(pUserData, riff2, sizeof(riff2)) != sizeof(riff2)) {
- return DR_FALSE;
- }
-
- for (int i = 0; i < 12; ++i) {
- if (riff2[i] != drwavGUID_W64_RIFF[i+4]) {
- return DR_FALSE;
- }
- }
- } else {
- return DR_FALSE; // Unknown or unsupported container.
- }
-
-
- if (pWav->container == drwav_container_riff) {
- // RIFF/WAVE
- unsigned char chunkSizeBytes[4];
- if (onRead(pUserData, chunkSizeBytes, sizeof(chunkSizeBytes)) != sizeof(chunkSizeBytes)) {
- return DR_FALSE;
- }
-
- unsigned int chunkSize = drwav__bytes_to_u32(chunkSizeBytes);
- if (chunkSize < 36) {
- return DR_FALSE; // Chunk size should always be at least 36 bytes.
- }
-
- unsigned char wave[4];
- if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) {
- return DR_FALSE;
- }
-
- if (!drwav__fourcc_equal(wave, "WAVE")) {
- return DR_FALSE; // Expecting "WAVE".
- }
- } else {
- // W64
- unsigned char chunkSize[8];
- if (onRead(pUserData, chunkSize, sizeof(chunkSize)) != sizeof(chunkSize)) {
- return DR_FALSE;
- }
-
- if (drwav__bytes_to_u64(chunkSize) < 84) {
- return DR_FALSE;
- }
-
- dr_uint8 wave[16];
- if (onRead(pUserData, wave, sizeof(wave)) != sizeof(wave)) {
- return DR_FALSE;
- }
-
- if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) {
- return DR_FALSE;
- }
- }
-
-
- // The next 24 bytes should be the "fmt " chunk.
- drwav_fmt fmt;
- if (!drwav__read_fmt(onRead, onSeek, pUserData, pWav->container, &fmt)) {
- return DR_FALSE; // Failed to read the "fmt " chunk.
- }
-
-
- // Translate the internal format.
- unsigned short translatedFormatTag = fmt.formatTag;
- if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) {
- translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0);
- }
-
-
- // The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop.
- dr_uint64 dataSize;
- for (;;)
- {
- drwav__chunk_header header;
- if (!drwav__read_chunk_header(onRead, pUserData, pWav->container, &header)) {
- return DR_FALSE;
- }
-
- dataSize = header.sizeInBytes;
- if (pWav->container == drwav_container_riff) {
- if (drwav__fourcc_equal(header.id.fourcc, "data")) {
- break;
- }
- } else {
- if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) {
- break;
- }
- }
-
- // If we get here it means we didn't find the "data" chunk. Seek past it.
-
- // Make sure we seek past the padding.
- dataSize += header.paddingSize;
- drwav__seek_forward(onSeek, dataSize, pUserData);
- }
-
- // At this point we should be sitting on the first byte of the raw audio data.
-
- pWav->onRead = onRead;
- pWav->onSeek = onSeek;
- pWav->pUserData = pUserData;
- pWav->fmt = fmt;
- pWav->sampleRate = fmt.sampleRate;
- pWav->channels = fmt.channels;
- pWav->bitsPerSample = fmt.bitsPerSample;
- pWav->bytesPerSample = (unsigned int)(fmt.blockAlign / fmt.channels);
- pWav->translatedFormatTag = translatedFormatTag;
- pWav->totalSampleCount = dataSize / pWav->bytesPerSample;
- pWav->bytesRemaining = dataSize;
-
- return DR_TRUE;
-}
-
-void drwav_uninit(drwav* pWav)
-{
- if (pWav == NULL) {
- return;
- }
-
-#ifndef DR_WAV_NO_STDIO
- // If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file()
- // was used by looking at the onRead and onSeek callbacks.
- if (pWav->onRead == drwav__on_read_stdio) {
- fclose((FILE*)pWav->pUserData);
- }
-#endif
-}
-
-
-drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData)
-{
- drwav* pWav = (drwav*)malloc(sizeof(*pWav));
- if (pWav == NULL) {
- return NULL;
- }
-
- if (!drwav_init(pWav, onRead, onSeek, pUserData)) {
- free(pWav);
- return NULL;
- }
-
- return pWav;
-}
-
-void drwav_close(drwav* pWav)
-{
- drwav_uninit(pWav);
- free(pWav);
-}
-
-
-size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut)
-{
- if (pWav == NULL || bytesToRead == 0 || pBufferOut == NULL) {
- return 0;
- }
-
- if (bytesToRead > pWav->bytesRemaining) {
- bytesToRead = (size_t)pWav->bytesRemaining;
- }
-
- size_t bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead);
-
- pWav->bytesRemaining -= bytesRead;
- return bytesRead;
-}
-
-dr_uint64 drwav_read(drwav* pWav, dr_uint64 samplesToRead, void* pBufferOut)
-{
- if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) {
- return 0;
- }
-
- // Don't try to read more samples than can potentially fit in the output buffer.
- if (samplesToRead * pWav->bytesPerSample > SIZE_MAX) {
- samplesToRead = SIZE_MAX / pWav->bytesPerSample;
- }
-
- size_t bytesRead = drwav_read_raw(pWav, (size_t)(samplesToRead * pWav->bytesPerSample), pBufferOut);
- return bytesRead / pWav->bytesPerSample;
-}
-
-dr_bool32 drwav_seek_to_sample(drwav* pWav, dr_uint64 sample)
-{
- // Seeking should be compatible with wave files > 2GB.
-
- if (pWav == NULL || pWav->onSeek == NULL) {
- return 0;
- }
-
- // If there are no samples, just return DR_TRUE without doing anything.
- if (pWav->totalSampleCount == 0) {
- return 1;
- }
-
- // Make sure the sample is clamped.
- if (sample >= pWav->totalSampleCount) {
- sample = pWav->totalSampleCount - 1;
- }
-
-
- dr_uint64 totalSizeInBytes = pWav->totalSampleCount * pWav->bytesPerSample;
- assert(totalSizeInBytes >= pWav->bytesRemaining);
-
- dr_uint64 currentBytePos = totalSizeInBytes - pWav->bytesRemaining;
- dr_uint64 targetBytePos = sample * pWav->bytesPerSample;
-
- dr_uint64 offset;
- int direction;
- if (currentBytePos < targetBytePos) {
- // Offset forward.
- offset = targetBytePos - currentBytePos;
- direction = 1;
- } else {
- // Offset backwards.
- offset = currentBytePos - targetBytePos;
- direction = -1;
- }
-
- while (offset > 0)
- {
- int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset);
- pWav->onSeek(pWav->pUserData, offset32 * direction, drwav_seek_origin_current);
-
- pWav->bytesRemaining -= (offset32 * direction);
- offset -= offset32;
- }
-
- return 1;
-}
-
-
-#ifndef DR_WAV_NO_CONVERSION_API
-#define drwav_min(a, b) (((a) < (b)) ? (a) : (b))
-
-static unsigned short g_drwavAlawTable[256] = {
- 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580,
- 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0,
- 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600,
- 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00,
- 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58,
- 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58,
- 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960,
- 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0,
- 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80,
- 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40,
- 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00,
- 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500,
- 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8,
- 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8,
- 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0,
- 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350
-};
-
-static unsigned short g_drwavMulawTable[256] = {
- 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84,
- 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84,
- 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004,
- 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844,
- 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64,
- 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74,
- 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C,
- 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000,
- 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C,
- 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C,
- 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC,
- 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC,
- 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C,
- 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C,
- 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084,
- 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000
-};
-
-
-dr_uint64 drwav_read_s16(drwav* pWav, dr_uint64 samplesToRead, dr_int16* pBufferOut)
-{
- // TODO: Optimize me. This is a temporary implementation for the moment.
- dr_uint64 totalSamplesRead = 0;
-
- while (samplesToRead > 0) {
- dr_int32 samples32[4096];
- dr_uint64 samplesJustRead = drwav_read_s32(pWav, (samplesToRead > 4096) ? 4096 : samplesToRead, samples32);
- if (samplesJustRead == 0) {
- break; // Reached the end.
- }
-
- // s32 -> s16
- for (dr_uint64 i = 0; i < samplesJustRead; ++i) {
- pBufferOut[i] = (dr_int16)(samples32[i] >> 16);
- }
-
- totalSamplesRead += samplesJustRead;
- samplesToRead -= samplesJustRead;
- pBufferOut += samplesJustRead;
- }
-
- return totalSamplesRead;
-}
-
-
-static int drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample)
-{
- if (pOut == NULL || pIn == NULL) {
- return 0;
- }
-
- // Special case for 8-bit sample data because it's treated as unsigned.
- if (bytesPerSample == 1) {
- drwav_u8_to_f32(pOut, pIn, sampleCount);
- return 1;
- }
-
-
- // Slightly more optimal implementation for common formats.
- if (bytesPerSample == 2) {
- drwav_s16_to_f32(pOut, (const dr_int16*)pIn, sampleCount);
- return 1;
- }
- if (bytesPerSample == 3) {
- drwav_s24_to_f32(pOut, pIn, sampleCount);
- return 1;
- }
- if (bytesPerSample == 4) {
- drwav_s32_to_f32(pOut, (const dr_int32*)pIn, sampleCount);
- return 1;
- }
-
-
- // Generic, slow converter.
- for (unsigned int i = 0; i < sampleCount; ++i)
- {
- unsigned int sample = 0;
- unsigned int shift = (8 - bytesPerSample) * 8;
- for (unsigned short j = 0; j < bytesPerSample && j < 4; ++j) {
- sample |= (unsigned int)(pIn[j]) << shift;
- shift += 8;
- }
-
- pIn += bytesPerSample;
- *pOut++ = (float)((int)sample / 2147483648.0);
- }
-
- return 1;
-}
-
-static int drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample)
-{
- if (pOut == NULL || pIn == NULL) {
- return 0;
- }
-
- if (bytesPerSample == 4) {
- for (unsigned int i = 0; i < sampleCount; ++i) {
- *pOut++ = ((float*)pIn)[i];
- }
- return 1;
- } else {
- drwav_f64_to_f32(pOut, (double*)pIn, sampleCount);
- return 1;
- }
-}
-
-
-dr_uint64 drwav_read_f32(drwav* pWav, dr_uint64 samplesToRead, float* pBufferOut)
-{
- if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) {
- return 0;
- }
-
- // Fast path.
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bytesPerSample == 4) {
- return drwav_read(pWav, samplesToRead, pBufferOut);
- }
-
-
- // Don't try to read more samples than can potentially fit in the output buffer.
- if (samplesToRead * sizeof(float) > SIZE_MAX) {
- samplesToRead = SIZE_MAX / sizeof(float);
- }
-
-
- // Slow path. Need to read and convert.
- dr_uint64 totalSamplesRead = 0;
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav_ulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- return totalSamplesRead;
-}
-
-void drwav_u8_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = (pIn[i] / 255.0f) * 2 - 1;
- }
-}
-
-void drwav_s16_to_f32(float* pOut, const dr_int16* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = pIn[i] / 32768.0f;
- }
-}
-
-void drwav_s24_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- unsigned int s0 = pIn[i*3 + 0];
- unsigned int s1 = pIn[i*3 + 1];
- unsigned int s2 = pIn[i*3 + 2];
-
- int sample32 = (int)((s0 << 8) | (s1 << 16) | (s2 << 24));
- *pOut++ = (float)(sample32 / 2147483648.0);
- }
-}
-
-void drwav_s32_to_f32(float* pOut, const dr_int32* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = (float)(pIn[i] / 2147483648.0);
- }
-}
-
-void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = (float)pIn[i];
- }
-}
-
-void drwav_alaw_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = g_drwavAlawTable[pIn[i]] / 32768.0f;
- }
-}
-
-void drwav_ulaw_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = g_drwavMulawTable[pIn[i]] / 32768.0f;
- }
-}
-
-
-
-static int drwav__pcm_to_s32(dr_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample)
-{
- if (pOut == NULL || pIn == NULL) {
- return 0;
- }
-
- // Special case for 8-bit sample data because it's treated as unsigned.
- if (bytesPerSample == 1) {
- drwav_u8_to_s32(pOut, pIn, totalSampleCount);
- return 1;
- }
-
-
- // Slightly more optimal implementation for common formats.
- if (bytesPerSample == 2) {
- drwav_s16_to_s32(pOut, (const dr_int16*)pIn, totalSampleCount);
- return 1;
- }
- if (bytesPerSample == 3) {
- drwav_s24_to_s32(pOut, pIn, totalSampleCount);
- return 1;
- }
- if (bytesPerSample == 4) {
- for (unsigned int i = 0; i < totalSampleCount; ++i) {
- *pOut++ = ((dr_int32*)pIn)[i];
- }
- return 1;
- }
-
-
- // Generic, slow converter.
- for (unsigned int i = 0; i < totalSampleCount; ++i)
- {
- unsigned int sample = 0;
- unsigned int shift = (8 - bytesPerSample) * 8;
- for (unsigned short j = 0; j < bytesPerSample && j < 4; ++j) {
- sample |= (unsigned int)(pIn[j]) << shift;
- shift += 8;
- }
-
- pIn += bytesPerSample;
- *pOut++ = sample;
- }
-
- return 1;
-}
-
-static int drwav__ieee_to_s32(dr_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample)
-{
- if (pOut == NULL || pIn == NULL) {
- return 0;
- }
-
- if (bytesPerSample == 4) {
- drwav_f32_to_s32(pOut, (float*)pIn, totalSampleCount);
- return 1;
- } else {
- drwav_f64_to_s32(pOut, (double*)pIn, totalSampleCount);
- return 1;
- }
-}
-
-dr_uint64 drwav_read_s32(drwav* pWav, dr_uint64 samplesToRead, dr_int32* pBufferOut)
-{
- if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) {
- return 0;
- }
-
- // Fast path.
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bytesPerSample == 4) {
- return drwav_read(pWav, samplesToRead, pBufferOut);
- }
-
-
- // Don't try to read more samples than can potentially fit in the output buffer.
- if (samplesToRead * sizeof(dr_int32) > SIZE_MAX) {
- samplesToRead = SIZE_MAX / sizeof(dr_int32);
- }
-
-
- // Slow path. Need to read and convert.
- dr_uint64 totalSamplesRead = 0;
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW)
- {
- unsigned char sampleData[4096];
- while (samplesToRead > 0)
- {
- dr_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData);
- if (samplesRead == 0) {
- break;
- }
-
- drwav_ulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead);
- pBufferOut += samplesRead;
-
- samplesToRead -= samplesRead;
- totalSamplesRead += samplesRead;
- }
-
- return totalSamplesRead;
- }
-
- return totalSamplesRead;
-}
-
-void drwav_u8_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = ((int)pIn[i] - 128) << 24;
- }
-}
-
-void drwav_s16_to_s32(dr_int32* pOut, const dr_int16* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = pIn[i] << 16;
- }
-}
-
-void drwav_s24_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- unsigned int s0 = pIn[i*3 + 0];
- unsigned int s1 = pIn[i*3 + 1];
- unsigned int s2 = pIn[i*3 + 2];
-
- dr_int32 sample32 = (dr_int32)((s0 << 8) | (s1 << 16) | (s2 << 24));
- *pOut++ = sample32;
- }
-}
-
-void drwav_f32_to_s32(dr_int32* pOut, const float* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = (dr_int32)(2147483648.0 * pIn[i]);
- }
-}
-
-void drwav_f64_to_s32(dr_int32* pOut, const double* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = (dr_int32)(2147483648.0 * pIn[i]);
- }
-}
-
-void drwav_alaw_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i = 0; i < sampleCount; ++i) {
- *pOut++ = ((dr_int32)g_drwavAlawTable[pIn[i]]) << 16;
- }
-}
-
-void drwav_ulaw_to_s32(dr_int32* pOut, const dr_uint8* pIn, size_t sampleCount)
-{
- if (pOut == NULL || pIn == NULL) {
- return;
- }
-
- for (size_t i= 0; i < sampleCount; ++i) {
- *pOut++ = ((dr_int32)g_drwavMulawTable[pIn[i]]) << 16;
- }
-}
-
-
-
-dr_int16* drwav__read_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- assert(pWav != NULL);
-
- dr_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(dr_int16);
- if (sampleDataSize > SIZE_MAX) {
- drwav_uninit(pWav);
- return NULL; // File's too big.
- }
-
- dr_int16* pSampleData = (dr_int16*)malloc((size_t)sampleDataSize); // <-- Safe cast due to the check above.
- if (pSampleData == NULL) {
- drwav_uninit(pWav);
- return NULL; // Failed to allocate memory.
- }
-
- dr_uint64 samplesRead = drwav_read_s16(pWav, (size_t)pWav->totalSampleCount, pSampleData);
- if (samplesRead != pWav->totalSampleCount) {
- free(pSampleData);
- drwav_uninit(pWav);
- return NULL; // There was an error reading the samples.
- }
-
- drwav_uninit(pWav);
-
- if (sampleRate) *sampleRate = pWav->sampleRate;
- if (channels) *channels = pWav->channels;
- if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount;
- return pSampleData;
-}
-
-float* drwav__read_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- assert(pWav != NULL);
-
- dr_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(float);
- if (sampleDataSize > SIZE_MAX) {
- drwav_uninit(pWav);
- return NULL; // File's too big.
- }
-
- float* pSampleData = (float*)malloc((size_t)sampleDataSize); // <-- Safe cast due to the check above.
- if (pSampleData == NULL) {
- drwav_uninit(pWav);
- return NULL; // Failed to allocate memory.
- }
-
- dr_uint64 samplesRead = drwav_read_f32(pWav, (size_t)pWav->totalSampleCount, pSampleData);
- if (samplesRead != pWav->totalSampleCount) {
- free(pSampleData);
- drwav_uninit(pWav);
- return NULL; // There was an error reading the samples.
- }
-
- drwav_uninit(pWav);
-
- if (sampleRate) *sampleRate = pWav->sampleRate;
- if (channels) *channels = pWav->channels;
- if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount;
- return pSampleData;
-}
-
-dr_int32* drwav__read_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- assert(pWav != NULL);
-
- dr_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(dr_int32);
- if (sampleDataSize > SIZE_MAX) {
- drwav_uninit(pWav);
- return NULL; // File's too big.
- }
-
- dr_int32* pSampleData = (dr_int32*)malloc((size_t)sampleDataSize); // <-- Safe cast due to the check above.
- if (pSampleData == NULL) {
- drwav_uninit(pWav);
- return NULL; // Failed to allocate memory.
- }
-
- dr_uint64 samplesRead = drwav_read_s32(pWav, (size_t)pWav->totalSampleCount, pSampleData);
- if (samplesRead != pWav->totalSampleCount) {
- free(pSampleData);
- drwav_uninit(pWav);
- return NULL; // There was an error reading the samples.
- }
-
- drwav_uninit(pWav);
-
- if (sampleRate) *sampleRate = pWav->sampleRate;
- if (channels) *channels = pWav->channels;
- if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount;
- return pSampleData;
-}
-
-
-dr_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init(&wav, onRead, onSeek, pUserData)) {
- return NULL;
- }
-
- return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
-}
-
-float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init(&wav, onRead, onSeek, pUserData)) {
- return NULL;
- }
-
- return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
-}
-
-dr_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init(&wav, onRead, onSeek, pUserData)) {
- return NULL;
- }
-
- return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
-}
-
-#ifndef DR_WAV_NO_STDIO
-dr_int16* drwav_open_and_read_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_file(&wav, filename)) {
- return NULL;
- }
-
- return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
-}
-
-float* drwav_open_and_read_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_file(&wav, filename)) {
- return NULL;
- }
-
- return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
-}
-
-dr_int32* drwav_open_and_read_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_file(&wav, filename)) {
- return NULL;
- }
-
- return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
-}
-#endif
-
-dr_int16* drwav_open_and_read_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_memory(&wav, data, dataSize)) {
- return NULL;
- }
-
- return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount);
-}
-
-float* drwav_open_and_read_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_memory(&wav, data, dataSize)) {
- return NULL;
- }
-
- return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount);
-}
-
-dr_int32* drwav_open_and_read_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount)
-{
- if (sampleRate) *sampleRate = 0;
- if (channels) *channels = 0;
- if (totalSampleCount) *totalSampleCount = 0;
-
- drwav wav;
- if (!drwav_init_memory(&wav, data, dataSize)) {
- return NULL;
- }
-
- return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount);
-}
-#endif //DR_WAV_NO_CONVERSION_API
-
-
-void drwav_free(void* pDataReturnedByOpenAndRead)
-{
- free(pDataReturnedByOpenAndRead);
-}
-
-#endif //DR_WAV_IMPLEMENTATION
-
-
-// REVISION HISTORY
-//
-// v0.5g - 2017-07-16
-// - Change underlying type for booleans to unsigned.
-//
-// v0.5f - 2017-04-04
-// - Fix a minor bug with drwav_open_and_read_s16() and family.
-//
-// v0.5e - 2016-12-29
-// - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this.
-// - Minor fixes to documentation.
-//
-// v0.5d - 2016-12-28
-// - Use dr_int*/dr_uint* sized types to improve compiler support.
-//
-// v0.5c - 2016-11-11
-// - Properly handle JUNK chunks that come before the FMT chunk.
-//
-// v0.5b - 2016-10-23
-// - A minor change to dr_bool8 and dr_bool32 types.
-//
-// v0.5a - 2016-10-11
-// - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering.
-// - Improve A-law and mu-law efficiency.
-//
-// v0.5 - 2016-09-29
-// - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to
-// keep it consistent with dr_audio and dr_flac.
-//
-// v0.4b - 2016-09-18
-// - Fixed a typo in documentation.
-//
-// v0.4a - 2016-09-18
-// - Fixed a typo.
-// - Change date format to ISO 8601 (YYYY-MM-DD)
-//
-// v0.4 - 2016-07-13
-// - API CHANGE. Make onSeek consistent with dr_flac.
-// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac.
-// - Added support for Sony Wave64.
-//
-// v0.3a - 2016-05-28
-// - API CHANGE. Return dr_bool32 instead of int in onSeek callback.
-// - Fixed a memory leak.
-//
-// v0.3 - 2016-05-22
-// - Lots of API changes for consistency.
-//
-// v0.2a - 2016-05-16
-// - Fixed Linux/GCC build.
-//
-// v0.2 - 2016-05-11
-// - Added support for reading data as signed 32-bit PCM for consistency with dr_flac.
-//
-// v0.1a - 2016-05-07
-// - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize.
-//
-// v0.1 - 2016-05-04
-// - Initial versioned release.
-
-
-/*
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-
-For more information, please refer to <http://unlicense.org/>
-*/ \ No newline at end of file
diff --git a/wav/wav.go b/wav/wav.go
deleted file mode 100644
index acd7841..0000000
--- a/wav/wav.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package wav
-
-// #define DR_WAV_IMPLEMENTATION
-// #include "dr_wav.h"
-import "C"
-import (
- "fmt"
-
- "sync"
-
- "github.com/op/go-logging"
- "layeh.com/gopus"
-)
-
-// number of individual samples per channel per batch
-const samplesPerChannelPerBatch = 1920
-
-var log = logging.MustGetLogger("wav")
-
-type Wav struct {
- filename *C.char
- enc *gopus.Encoder
- wav *C.drwav
-
- once sync.Once
- Done <-chan struct{}
- done chan<- struct{}
-}
-
-func New(filename string) (*Wav, error) {
- cfname := C.CString(filename)
- wav := C.drwav_open_file(cfname)
- if wav == nil {
- C.drwav_close(wav)
- return nil, fmt.Errorf("Unable to initialize drwav.")
- }
-
- if int(wav.channels) != 2 {
- C.drwav_close(wav)
- return nil, fmt.Errorf("Wrong number of channels!")
- }
-
- if int(wav.sampleRate) != 48000 {
- C.drwav_close(wav)
- return nil, fmt.Errorf("Wrong sample rate.")
- }
-
- enc, err := gopus.NewEncoder(int(wav.sampleRate), int(wav.channels), gopus.Audio)
- if err != nil {
- C.drwav_close(wav)
- return nil, err
- }
-
- done := make(chan struct{}, 1)
- return &Wav{
- filename: C.CString(filename),
- enc: enc,
- wav: wav,
-
- done: done,
- Done: done,
- }, nil
-}
-
-func (w *Wav) Stop() {
- w.once.Do(func() {
- close(w.done)
- })
-}
-
-func (w *Wav) Start(ch chan<- []byte) {
- go func() {
- defer w.Stop()
-
- samplesPerBatch := samplesPerChannelPerBatch * int(w.wav.channels)
- batchSize := samplesPerBatch * int(w.wav.bytesPerSample)
-
- buf := C.malloc(C.size_t(batchSize))
- defer C.free(buf)
- defer C.drwav_close(w.wav)
-
- elems := make([]int16, samplesPerBatch)
- idx := 0
-
- for i := 0; i*samplesPerBatch <= int(w.wav.totalSampleCount); i += 1 {
- readSamples := C.drwav_read_s16(w.wav, C.dr_uint64(samplesPerBatch), (*C.dr_int16)(buf))
- slc := (*[1 << 30]int16)(buf)[:readSamples:readSamples]
-
- readIdx := 0
-
- for {
- batchSamplesToFill := samplesPerBatch - idx
- readSamplesRemaining := int(readSamples) - readIdx
-
- // break if we don't have enough samples to fill the rest of the buffer
- if readSamplesRemaining < batchSamplesToFill {
- break
- }
-
- copy(elems[idx:], slc[readIdx:readIdx+batchSamplesToFill])
- idx = 0
- readIdx += batchSamplesToFill
-
- b, err := processPCM(w.wav, w.enc, elems[:])
- if err != nil {
- log.Errorf("error encoding pcm: %q", err)
- continue
- }
-
- select {
- case <-w.Done:
- return
- default:
- }
- select {
- case <-w.Done:
- return
- case ch <- b:
- }
- }
-
- batchSamplesToFill := samplesPerBatch - idx
- readSamplesRemaining := int(readSamples) - readIdx
- if readSamplesRemaining >= batchSamplesToFill {
- log.Fatalf("Had enough samples to fill batch after for loop.")
- }
-
- copy(elems[idx:], slc)
- idx += len(slc)
-
- if int(readSamples) < samplesPerBatch {
- break
- }
- }
- }()
-}
-
-func processPCM(wav *C.drwav, enc *gopus.Encoder, data []int16) ([]byte, error) {
- return enc.Encode(data, len(data)/int(wav.channels), len(data)*2)
-}
diff --git a/wav/wav_test.go b/wav/wav_test.go
deleted file mode 100644
index 5d6bef7..0000000
--- a/wav/wav_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package wav
-
-import (
- "fmt"
- "testing"
-)
-
-func TestLoad(t *testing.T) {
- ch, err := Load("../downloader/out.wav")
- if err != nil {
- t.Fatal(err)
- }
-
- ct := 0
-
- for _ = range ch {
- //fmt.Println(i)
- ct++
- if ct%10000 == 0 {
- fmt.Println(ct)
- }
- }
-
- fmt.Println("COUNT: ", ct)
-}