Jelajahi Sumber

Initial commit

Frans Bergman 4 tahun lalu
melakukan
10e783b12c
5 mengubah file dengan 1211 tambahan dan 0 penghapusan
  1. 1 0
      .gitignore
  2. 809 0
      Cargo.lock
  3. 19 0
      Cargo.toml
  4. 37 0
      README.md
  5. 345 0
      src/main.rs

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/target

+ 809 - 0
Cargo.lock

@@ -0,0 +1,809 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "argparse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "bytes"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
+
+[[package]]
+name = "cc"
+version = "1.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "core-foundation"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+dependencies = [
+ "bitflags",
+ "fuchsia-zircon-sys",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+
+[[package]]
+name = "futures"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
+
+[[package]]
+name = "futures-task"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "futures-util"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "mio"
+version = "0.6.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
+dependencies = [
+ "cfg-if",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow 0.2.1",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
+]
+
+[[package]]
+name = "mio-named-pipes"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
+dependencies = [
+ "log",
+ "mio",
+ "miow 0.3.5",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mio-uds"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
+dependencies = [
+ "iovec",
+ "libc",
+ "mio",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+dependencies = [
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
+dependencies = [
+ "socket2",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mumble-bot-rs"
+version = "0.1.0"
+dependencies = [
+ "argparse",
+ "bytes",
+ "futures",
+ "mumble-protocol",
+ "native-tls",
+ "opus",
+ "tokio",
+ "tokio-tls",
+ "tokio-util",
+]
+
+[[package]]
+name = "mumble-protocol"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb966bde3fc52998b9b74522e8aa22826823cc69cbf672893f79115195c76921"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "openssl",
+ "protobuf",
+ "protobuf-codegen-pure",
+ "tokio-util",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
+
+[[package]]
+name = "openssl"
+version = "0.10.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "lazy_static",
+ "libc",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "opus"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e9059a7daf1e6665eb88c4a95ca9ff393a6d276fd66e9d85191280d7e5ec18b"
+dependencies = [
+ "libc",
+ "opus-sys",
+]
+
+[[package]]
+name = "opus-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad8b294f482f7972fa466b1c64d5a564e4ee6975599d80483ee4fa83f25b6ec"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "protobuf"
+version = "2.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d147edb77bcccbfc81fabffdc7bd50c13e103b15ca1e27515fe40de69a5776b"
+
+[[package]]
+name = "protobuf-codegen"
+version = "2.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e81f70c25aab9506f87253c55f7cdcd8917635d5597382958d20025c211bbbd"
+dependencies = [
+ "protobuf",
+]
+
+[[package]]
+name = "protobuf-codegen-pure"
+version = "2.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6af8d72d9e14fd41a954f4d5b310396151437c83d2bfcbf19d3073af90e46288"
+dependencies = [
+ "protobuf",
+ "protobuf-codegen",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "security-framework"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+
+[[package]]
+name = "socket2"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio",
+ "mio-named-pipes",
+ "mio-uds",
+ "num_cpus",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "tokio-macros",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]

+ 19 - 0
Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "mumble-bot-rs"
+version = "0.1.0"
+authors = ["Frans Bergman <frans@tankernn.eu>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+mumble-protocol = "0.3"
+opus = "0.2"
+argparse = "0.2"
+futures = "0.3"
+native-tls = "0.2"
+tokio = { version = "0.2", features = ["full"] }
+tokio-util = { version = "0.3", features = ["codec", "udp"] }
+tokio-tls = "0.3"
+bytes = "0.5"
+

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+# mumble-bot-rs
+
+A simple music bot that reads a FIFO to stream music to a Mumble server.
+
+## Building
+
+```
+cargo build
+```
+
+## Usage
+First, configure a music player (e.g. MPD) to write to a FIFO:
+
+mpd.conf
+```
+audio_output {
+    type        "fifo"
+    name        "FIFO-1"
+    path        "/tmp/mpd.fifo"
+    format      "48000:16:2"
+    mixer_type  "software"
+}
+
+```
+
+Then start the bot:
+
+```
+./mumble-bot-rs --host example.com --fifo-path /tmp/mpd.fifo
+```
+
+For additional options:
+```
+./mumble-bot-rs --help
+
+```
+

+ 345 - 0
src/main.rs

@@ -0,0 +1,345 @@
+extern crate argparse;
+extern crate bytes;
+extern crate futures;
+extern crate mumble_protocol;
+extern crate opus;
+
+use argparse::ArgumentParser;
+use argparse::Store;
+use argparse::StoreTrue;
+use bytes::Bytes;
+use futures::channel::oneshot;
+use futures::join;
+use futures::stream::SplitSink;
+use futures::SinkExt;
+use futures::StreamExt;
+use mumble_protocol::control::msgs;
+use mumble_protocol::control::ClientControlCodec;
+use mumble_protocol::control::ControlPacket;
+use mumble_protocol::crypt::ClientCryptState;
+use mumble_protocol::crypt::CryptState;
+use mumble_protocol::voice::VoicePacket;
+use mumble_protocol::voice::VoicePacketPayload;
+use mumble_protocol::Clientbound;
+use mumble_protocol::Serverbound;
+use opus::Encoder;
+use std::convert::Into;
+use std::convert::TryInto;
+use std::fs::File;
+use std::io::Read;
+use std::net::Ipv6Addr;
+use std::net::SocketAddr;
+use std::net::ToSocketAddrs;
+use std::thread;
+use std::time;
+use tokio::net::TcpStream;
+use tokio::net::UdpSocket;
+use tokio::sync::mpsc;
+use tokio_tls::TlsConnector;
+use tokio_util::codec::Decoder;
+use tokio_util::udp::UdpFramed;
+
+struct AudioSettings {
+    fifo_path: String,
+    bitrate: i32,
+}
+
+async fn connect(
+    server_addr: SocketAddr,
+    server_host: String,
+    user_name: String,
+    accept_invalid_cert: bool,
+    crypt_state_sender: oneshot::Sender<ClientCryptState>,
+) {
+    // Wrap crypt_state_sender in Option, so we can call it only once
+    let mut crypt_state_sender = Some(crypt_state_sender);
+
+    // Connect to server via TCP
+    let stream = TcpStream::connect(&server_addr)
+        .await
+        .expect("Failed to connect to server:");
+    println!("TCP connected..");
+
+    // Wrap the connection in TLS
+    let mut builder = native_tls::TlsConnector::builder();
+    builder.danger_accept_invalid_certs(accept_invalid_cert);
+    let connector: TlsConnector = builder
+        .build()
+        .expect("Failed to create TLS connector")
+        .into();
+    let tls_stream = connector
+        .connect(&server_host, stream)
+        .await
+        .expect("Failed to connect TLS: {}");
+    println!("TLS connected..");
+
+    // Wrap the TLS stream with Mumble's client-side control-channel codec
+    let (mut sink, mut stream) = ClientControlCodec::new().framed(tls_stream).split();
+
+    // Handshake (omitting `Version` message for brevity)
+    let mut msg = msgs::Authenticate::new();
+    msg.set_username(user_name);
+    msg.set_opus(true);
+    sink.send(msg.into()).await.unwrap();
+
+    println!("Logging in..");
+    let mut crypt_state = None;
+
+    // Channel for packet events and periodic ping events
+    let (tx, mut rx) = mpsc::channel(100);
+    let mut tx1 = tx.clone();
+    let mut tx2 = tx.clone();
+
+    // Handle incoming packets
+    tokio::spawn(async move {
+        while let Some(packet) = stream.next().await {
+            tx1.send(Some(packet)).await.unwrap()
+        }
+    });
+
+    // Generate periodic tick events
+    tokio::spawn(async move {
+        loop {
+            tx2.send(None).await.unwrap();
+            thread::sleep(time::Duration::from_millis(2000));
+        }
+    });
+
+    while let Some(i) = rx.recv().await {
+        match i {
+            Some(packet) => match packet.unwrap() {
+                ControlPacket::TextMessage(mut msg) => {
+                    println!(
+                        "Got message from user with session ID {}: {}",
+                        msg.get_actor(),
+                        msg.get_message()
+                    );
+                    // Send reply back to server
+                    let mut response = msgs::TextMessage::new();
+                    response.mut_session().push(msg.get_actor());
+                    response.set_message(msg.take_message());
+                    sink.send(response.into()).await.unwrap();
+                }
+                ControlPacket::CryptSetup(msg) => {
+                    // Wait until we're fully connected before initiating UDP voice
+                    crypt_state = Some(ClientCryptState::new_from(
+                        msg.get_key()
+                            .try_into()
+                            .expect("Server sent private key with incorrect size"),
+                        msg.get_client_nonce()
+                            .try_into()
+                            .expect("Server sent client_nonce with incorrect size"),
+                        msg.get_server_nonce()
+                            .try_into()
+                            .expect("Server sent server_nonce with incorrect size"),
+                    ));
+                }
+                ControlPacket::ServerSync(_) => {
+                    println!("Logged in!");
+                    if let Some(sender) = crypt_state_sender.take() {
+                        let _ = sender.send(
+                            crypt_state
+                                .take()
+                                .expect("Server didn't send us any CryptSetup packet!"),
+                        );
+                    }
+                }
+                ControlPacket::Reject(msg) => {
+                    println!("Login rejected: {:?}", msg);
+                }
+                _ => {}
+            },
+            None => {
+                sink.send(msgs::Ping::new().into()).await.unwrap();
+            }
+        }
+    }
+}
+
+async fn handle_udp(
+    server_addr: SocketAddr,
+    crypt_state: oneshot::Receiver<ClientCryptState>,
+    audio_settings: AudioSettings,
+) {
+    // Bind UDP socket
+    let udp_socket = UdpSocket::bind((Ipv6Addr::from(0u128), 0u16))
+        .await
+        .expect("Failed to bind UDP socket");
+
+    // Wait for initial CryptState
+    let crypt_state = match crypt_state.await {
+        Ok(crypt_state) => crypt_state,
+        // disconnected before we received the CryptSetup packet, oh well
+        Err(_) => return,
+    };
+    println!("UDP ready!");
+
+    // Wrap the raw UDP packets in Mumble's crypto and voice codec (CryptState does both)
+    let (mut sink, mut source) = UdpFramed::new(udp_socket, crypt_state).split();
+
+    tokio::spawn(async move { send_audio(&mut sink, server_addr, audio_settings).await });
+
+    // Handle incoming UDP packets
+    while let Some(packet) = source.next().await {
+        let (packet, _src_addr) = match packet {
+            Ok(packet) => packet,
+            Err(err) => {
+                eprintln!("Got an invalid UDP packet: {}", err);
+                // To be expected, considering this is the internet, just ignore it
+                continue;
+            }
+        };
+        match packet {
+            VoicePacket::Ping { .. } => {
+                // Note: A normal application would handle these and only use UDP for voice
+                //       once it has received one.
+                println!("Pong");
+                continue;
+            }
+            VoicePacket::Audio { .. } => {
+                // Got audio, do nothing
+            }
+        }
+    }
+}
+
+async fn send_audio(
+    sink: &mut SplitSink<
+        UdpFramed<CryptState<Serverbound, Clientbound>>,
+        (VoicePacket<Serverbound>, std::net::SocketAddr),
+    >,
+    server_addr: SocketAddr,
+    audio_settings: AudioSettings,
+) {
+    // Connect to unix named pipe
+    let mut fifo = match File::open(&audio_settings.fifo_path) {
+        Ok(f) => f,
+        Err(e) => {
+            eprintln!("Error opening FIFO at {}: {}", &audio_settings.fifo_path, e);
+            return;
+        }
+    };
+
+    // Init encoder
+    let mut encoder =
+        Encoder::new(48_000, opus::Channels::Stereo, opus::Application::Audio).unwrap();
+    encoder.set_packet_loss_perc(10).unwrap();
+    encoder
+        .set_bitrate(opus::Bitrate::Bits(audio_settings.bitrate))
+        .unwrap();
+
+    // Must be multiple of 240 when sampling at 48kHz
+    const BUF_SIZE: usize = 240 * 8;
+
+    let mut buf = [0; BUF_SIZE];
+    let mut opus_buf = [0; 72_000];
+    let mut seq_num = 0;
+
+    loop {
+        match fifo.read_exact(&mut buf) {
+            Ok(()) => {
+                // Convert two u8 into a single i16
+                let samples: Vec<i16> = buf
+                    .chunks(2)
+                    .into_iter()
+                    .map(|chunk| match chunk {
+                        [b, a] => ((*a as i16) << 8) + (*b as i16),
+                        _ => panic!(
+                            "BUG: Should be impossible, check that BUF_SIZE={} is even",
+                            BUF_SIZE
+                        ),
+                    })
+                    .collect();
+
+                // Encode with Opus
+                let payload = match encoder.encode(&samples, &mut opus_buf) {
+                    Ok(size) => {
+                        VoicePacketPayload::Opus(Bytes::from(opus_buf[0..size].to_owned()), false)
+                    }
+                    Err(e) => {
+                        println!("{:?}", e);
+                        continue;
+                    }
+                };
+
+                // Send!
+                let reply = VoicePacket::Audio {
+                    _dst: std::marker::PhantomData,
+                    target: 0,      // normal speech
+                    session_id: (), // unused for server-bound packets
+                    seq_num,
+                    payload,
+                    position_info: None,
+                };
+                sink.send((reply, server_addr)).await.unwrap();
+                seq_num += 1;
+            }
+            Err(e) => {
+                eprintln!("Error reading FIFO: {}", e);
+                break;
+            }
+        }
+    }
+}
+
+#[tokio::main]
+async fn main() {
+    // Handle command line arguments
+    let mut server_host = "".to_string();
+    let mut server_port = 64738u16;
+    let mut user_name = "mumble-bot-rs".to_string();
+    let mut accept_invalid_cert = false;
+    let mut bitrate = 96_000i32;
+    let mut fifo_path = "".to_string();
+    {
+        let mut ap = ArgumentParser::new();
+        ap.set_description("Mumble bot for streaming music from MPD");
+        ap.refer(&mut server_host)
+            .add_option(&["--host"], Store, "Hostname of mumble server")
+            .required();
+        ap.refer(&mut server_port)
+            .add_option(&["--port"], Store, "Port of mumble server");
+        ap.refer(&mut user_name)
+            .add_option(&["--username"], Store, "User name used to connect");
+        ap.refer(&mut accept_invalid_cert).add_option(
+            &["--accept-invalid-cert"],
+            StoreTrue,
+            "Accept invalid TLS certificates",
+        );
+        ap.refer(&mut fifo_path)
+            .add_option(
+                &["--fifo-path"],
+                Store,
+                "Path to FIFO to read PCM audio data from",
+            )
+            .required();
+        ap.refer(&mut bitrate)
+            .add_option(&["--bitrate"], Store, "Opus encoder bitrate (bits/s)");
+        ap.parse_args_or_exit();
+    }
+    let server_addr = (server_host.as_ref(), server_port)
+        .to_socket_addrs()
+        .expect("Failed to parse server address")
+        .next()
+        .expect("Failed to resolve server address");
+
+    // Oneshot channel for setting UDP CryptState from control task
+    // For simplicity we don't deal with re-syncing, real applications would have to.
+    let (crypt_state_sender, crypt_state_receiver) = oneshot::channel::<ClientCryptState>();
+
+    // Run it
+    join!(
+        connect(
+            server_addr,
+            server_host,
+            user_name,
+            accept_invalid_cert,
+            crypt_state_sender,
+        ),
+        handle_udp(
+            server_addr,
+            crypt_state_receiver,
+            AudioSettings { fifo_path, bitrate }
+        )
+    );
+}