commit 6a77189343cae46ca8a2199dc2aa39b6cef18f4a Author: DaXcess Date: Tue Oct 18 22:59:32 2022 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d04cad5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Rust +/target + +# SQLite database +*.db +*.sqlite + +# Secrets +.env \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..6f2e075 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 2 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..485151c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3215 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core 0.6.3", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", +] + +[[package]] +name = "aes-ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", + "ctr", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher 0.2.5", + "opaque-debug", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "audiopus" +version = "0.3.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab55eb0e56d7c6de3d59f544e5db122d7725ec33be6a276ee8241f3be6473955" +dependencies = [ + "audiopus_sys", +] + +[[package]] +name = "audiopus_sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651" +dependencies = [ + "cmake", + "log", + "pkg-config", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytemuck" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits 0.2.15", + "serde", + "time 0.1.44", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + +[[package]] +name = "command_attr" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d999d4e7731150ee14aee8f619c7a9aa9a4385bca0606c4fa95aa2f36a05d9a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher 0.2.5", +] + +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown", + "lock_api", + "parking_lot_core", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "discortp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66017646a48220b5ea30d63ac18bb5952f647f1a41ed755880895125d26972" +dependencies = [ + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin 0.9.4", +] + +[[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 = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-test" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3e9379dbbfb35dd6df79e895d73c0f75558827fe68eb853b858ff417a8ee98" +dependencies = [ + "futures-core", + "futures-executor", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "futures-util", + "pin-project", + "pin-utils", +] + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-proxy" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +dependencies = [ + "bytes", + "futures", + "headers", + "http", + "hyper", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if-addrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipc-channel" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cb1d9211085f0ea6f1379d944b93c4d07e8207aa3bcf49f37eda12b85081887" +dependencies = [ + "bincode", + "crossbeam-channel", + "fnv", + "futures", + "futures-test", + "lazy_static", + "libc", + "mio 0.6.23", + "rand 0.7.3", + "serde", + "tempfile", + "uuid 0.8.2", + "winapi 0.3.9", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[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 = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libm" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" + +[[package]] +name = "libmdns" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cfa684d4e75145d6c1fbe9a2547c2fa4d4d4e5bd6eacce779fd86d5e1cd7cbe" +dependencies = [ + "byteorder", + "futures-util", + "hostname", + "if-addrs", + "log", + "multimap", + "rand 0.8.5", + "socket2", + "thiserror", + "tokio", +] + +[[package]] +name = "librespot" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4c9952ef48968f8184a4a87f8576982426ebe623342d5a28f7d9c4978e4a44" +dependencies = [ + "base64", + "env_logger", + "futures-util", + "getopts", + "hex", + "hyper", + "librespot-audio", + "librespot-connect", + "librespot-core", + "librespot-discovery", + "librespot-metadata", + "librespot-playback", + "librespot-protocol", + "log", + "rpassword", + "sha-1 0.9.8", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "librespot-audio" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c176a31355e1ea8e0b9c4ced19df4947bfe4770661c25c142b6fba2365940d9d" +dependencies = [ + "aes-ctr", + "byteorder", + "bytes", + "futures-util", + "librespot-core", + "log", + "tempfile", + "tokio", +] + +[[package]] +name = "librespot-connect" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffafb6a443e9445ccb3d5d591573b5b1da3c89a9b8846c63ba2c3710210d3ec" +dependencies = [ + "form_urlencoded", + "futures-util", + "librespot-core", + "librespot-discovery", + "librespot-playback", + "librespot-protocol", + "log", + "protobuf", + "rand 0.8.5", + "serde", + "serde_json", + "tokio", + "tokio-stream", +] + +[[package]] +name = "librespot-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046349f25888e644bf02d9c5de0164b2a493d29aa4ce18e1ad0b756da9b55d6d" +dependencies = [ + "aes", + "base64", + "byteorder", + "bytes", + "form_urlencoded", + "futures-core", + "futures-util", + "hmac", + "http", + "httparse", + "hyper", + "hyper-proxy", + "librespot-protocol", + "log", + "num-bigint", + "num-integer", + "num-traits 0.2.15", + "once_cell", + "pbkdf2", + "priority-queue", + "protobuf", + "rand 0.8.5", + "serde", + "serde_json", + "sha-1 0.9.8", + "shannon", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "url", + "uuid 1.1.2", + "vergen", +] + +[[package]] +name = "librespot-discovery" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa877d18f6150364012cb4be5682d62d7c712c88bae2d0d01720fd7c15e2f06" +dependencies = [ + "aes-ctr", + "base64", + "form_urlencoded", + "futures-core", + "hmac", + "hyper", + "libmdns", + "librespot-core", + "log", + "rand 0.8.5", + "serde_json", + "sha-1 0.9.8", + "thiserror", + "tokio", +] + +[[package]] +name = "librespot-metadata" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b80361fcbcb5092056fd47c08c34d5d51b08385d8efb6941c0d3e46d032c21c" +dependencies = [ + "async-trait", + "byteorder", + "librespot-core", + "librespot-protocol", + "log", + "protobuf", +] + +[[package]] +name = "librespot-playback" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190a0b9bcc7f70ee4196a6b4a1c731d405ca130d4a6fcd4c561cfdde8b7cfb7" +dependencies = [ + "byteorder", + "futures-executor", + "futures-util", + "lewton", + "librespot-audio", + "librespot-core", + "librespot-metadata", + "log", + "ogg", + "parking_lot", + "rand 0.8.5", + "rand_distr", + "shell-words", + "thiserror", + "tokio", + "zerocopy", +] + +[[package]] +name = "librespot-protocol" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d3ac6196ac0ea67bbe039f56d6730a5d8b31502ef9bce0f504ed729dcb39f" +dependencies = [ + "glob", + "protobuf", + "protobuf-codegen-pure", +] + +[[package]] +name = "libsamplerate-sys" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28853b399f78f8281cd88d333b54a63170c4275f6faea66726a2bea5cca72e0d" +dependencies = [ + "cmake", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if 1.0.0", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.15", + "rand 0.8.5", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits 0.2.15", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.15", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits 0.2.15", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "backtrace", + "cfg-if 1.0.0", + "libc", + "petgraph", + "redox_syscall", + "smallvec", + "thread-id", + "windows-sys", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[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.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "pnet_base" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25488cd551a753dcaaa6fffc9f69a7610a412dd8954425bf7ffad5f7d1156fb8" + +[[package]] +name = "pnet_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30490e0852e58402b8fae0d39897b08a24f493023a4d6cf56b2e30f31ed57548" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4714e10f30cab023005adce048f2d30dd4ac4f093662abf2220855655ef8f90" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "priority-queue" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815082d99af3acc75a3e67efd2a07f72e67b4e81b4344eb8ca34c6ebf3dfa9c5" +dependencies = [ + "autocfg", + "indexmap", +] + +[[package]] +name = "proc-macro2" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" + +[[package]] +name = "protobuf-codegen" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec1632b7c8f2e620343439a7dfd1f3c47b18906c4be58982079911482b5d707" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8122fdb18e55190c796b088a16bdb70cd7acdcd48f7a8b796b58c62e532cc6" +dependencies = [ + "protobuf", + "protobuf-codegen", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[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 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits 0.2.15", + "rand 0.8.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[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 = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rpassword" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956" +dependencies = [ + "libc", + "serde", + "serde_json", + "winapi 0.3.9", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "salsa20" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0fbb5f676da676c260ba276a8f43a8dc67cf02d1438423aeb1c677a7212686" +dependencies = [ + "cipher 0.3.0", + "zeroize", +] + +[[package]] +name = "samplerate" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e032b2b24715c4f982f483ea3abdb3c9ba444d9f63e87b2843d6f998f5ba2698" +dependencies = [ + "libsamplerate-sys", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serenity" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" +dependencies = [ + "async-trait", + "async-tungstenite", + "base64", + "bitflags", + "bytes", + "cfg-if 1.0.0", + "chrono", + "command_attr", + "dashmap", + "flate2", + "futures", + "levenshtein", + "mime", + "mime_guess", + "parking_lot", + "percent-encoding", + "reqwest", + "serde", + "serde-value", + "serde_json", + "static_assertions", + "time 0.3.12", + "tokio", + "tracing", + "typemap_rev", + "url", + "uwl", +] + +[[package]] +name = "serenity-voice-model" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be3aec8849ca2fde1e8a5dfbed96fbd68e9b5f4283fbe277d8694ce811d4952" +dependencies = [ + "bitflags", + "enum_primitive", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha1" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "shannon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea5b41c9427b56caa7b808cb548a04fb50bb5b9e98590b53f28064ff4174561" +dependencies = [ + "byteorder", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "songbird" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4c965f6625a2653e0733abfe217679562eb8c4787f000f4f13047541a444217" +dependencies = [ + "async-trait", + "async-tungstenite", + "audiopus", + "byteorder", + "dashmap", + "derivative", + "discortp", + "flume", + "futures", + "parking_lot", + "pin-project", + "rand 0.8.5", + "serde", + "serde_json", + "serenity", + "serenity-voice-model", + "streamcatcher", + "symphonia-core", + "tokio", + "tracing", + "tracing-futures", + "typemap_rev", + "url", + "uuid 0.8.2", + "xsalsa20poly1305", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spoticord" +version = "2.0.0-indev" +dependencies = [ + "chrono", + "dotenv", + "env_logger", + "ipc-channel", + "librespot", + "log", + "reqwest", + "samplerate", + "serde", + "serde_json", + "serenity", + "shell-words", + "songbird", + "thiserror", + "tokio", + "zerocopy", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streamcatcher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71664755c349abb0758fda6218fb2d2391ca2a73f9302c03b145491db4fcea29" +dependencies = [ + "crossbeam-utils 0.8.11", + "futures-util", + "loom", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "symphonia-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "199a6417cd4115bac79289b64b859358ea050b7add0ceb364dc991f628c5b347" +dependencies = [ + "arrayvec", + "bitflags", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread-id" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" +dependencies = [ + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f" +dependencies = [ + "itoa", + "js-sys", + "libc", + "num_threads", + "serde", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio 0.8.4", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls", + "sha-1 0.10.0", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typemap_rev" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "uwl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7141e445af09c8919f1d5f8a20dae0b20c3b57a45dee0d5823c6ed5d237f15a" +dependencies = [ + "bitflags", + "chrono", + "rustc_version", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "web-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +dependencies = [ + "webpki", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[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 = "windows" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + +[[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", +] + +[[package]] +name = "xsalsa20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68bcb965d6c650091450b95cea12f07dcd299a01c15e2f9433b0813ea3c0886" +dependencies = [ + "aead", + "poly1305", + "rand_core 0.6.3", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "zerocopy" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5b99233 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "spoticord" +version = "2.0.0-indev" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +lto = true +codegen-units = 1 +strip = true +opt-level = "z" + +[dependencies] +chrono = "0.4.22" +dotenv = "0.15.0" +env_logger = "0.9.0" +ipc-channel = { version = "0.16.0", features = ["async"] } +librespot = { version = "0.4.2", default-features = false } +log = "0.4.17" +reqwest = "0.11.11" +samplerate = "0.2.4" +serde = "1.0.144" +serde_json = "1.0.85" +serenity = { version = "0.11.5", features = ["voice"] } +shell-words = "1.1.0" +songbird = "0.3.0" +thiserror = "1.0.33" +tokio = { version = "1.20.1", features = ["rt", "full"] } +zerocopy = "0.6.1" diff --git a/src/audio/backend.rs b/src/audio/backend.rs new file mode 100644 index 0000000..328037f --- /dev/null +++ b/src/audio/backend.rs @@ -0,0 +1,167 @@ +use librespot::playback::audio_backend::{Sink, SinkAsBytes, SinkResult}; +use librespot::playback::convert::Converter; +use librespot::playback::decoder::AudioPacket; +use log::{error, trace}; +use std::io::Write; +use std::sync::{Arc, Mutex}; +use std::thread::JoinHandle; +use std::time::Duration; + +use crate::ipc; +use crate::ipc::packet::IpcPacket; + +pub struct StdoutSink { + client: ipc::Client, + buffer: Arc>>, + is_stopped: Arc>, + handle: Option>, +} + +const BUFFER_SIZE: usize = 7680; + +impl StdoutSink { + pub fn start_writer(&mut self) { + // With 48khz, 32-bit float, 2 channels, 1 second of audio is 384000 bytes + // 384000 / 50 = 7680 bytes per 20ms + + let buffer = self.buffer.clone(); + let is_stopped = self.is_stopped.clone(); + let client = self.client.clone(); + + let handle = std::thread::spawn(move || { + let mut output = std::io::stdout(); + let mut act_buffer = [0u8; BUFFER_SIZE]; + + // Use closure to make sure lock is released as fast as possible + let is_stopped = || { + let is_stopped = is_stopped.lock().unwrap(); + *is_stopped + }; + + // Start songbird's playback + client.send(IpcPacket::StartPlayback).unwrap(); + + loop { + if is_stopped() { + break; + } + + std::thread::sleep(Duration::from_millis(15)); + + let mut buffer = buffer.lock().unwrap(); + let to_drain: usize; + + if buffer.len() < BUFFER_SIZE { + // Copy the buffer into the action buffer + // Fill remaining length with zeroes + act_buffer[..buffer.len()].copy_from_slice(&buffer[..]); + act_buffer[buffer.len()..].fill(0); + + to_drain = buffer.len(); + } else { + act_buffer.copy_from_slice(&buffer[..BUFFER_SIZE]); + to_drain = BUFFER_SIZE; + } + + output.write_all(&act_buffer).unwrap_or(()); + buffer.drain(..to_drain); + } + }); + + self.handle = Some(handle); + } + + pub fn stop_writer(&mut self) -> std::thread::Result<()> { + // Use closure to avoid deadlocking the mutex + let set_stopped = |value| { + let mut is_stopped = self.is_stopped.lock().unwrap(); + *is_stopped = value; + }; + + // Notify thread to stop + set_stopped(true); + + // Wait for thread to stop + let result = match self.handle.take() { + Some(handle) => handle.join(), + None => Ok(()), + }; + + // Reset stopped value + set_stopped(false); + + result + } + + pub fn new(client: ipc::Client) -> Self { + StdoutSink { + client, + is_stopped: Arc::new(Mutex::new(false)), + buffer: Arc::new(Mutex::new(Vec::new())), + handle: None, + } + } +} + +impl Sink for StdoutSink { + fn start(&mut self) -> SinkResult<()> { + self.start_writer(); + + Ok(()) + } + + fn stop(&mut self) -> SinkResult<()> { + // Stop the writer thread + // This is done before pausing songbird, because else the writer thread + // might hang on writing to stdout + if let Err(why) = self.stop_writer() { + error!("Failed to stop stdout writer: {:?}", why); + } else { + trace!("Stopped stdout writer"); + } + + // Stop songbird's playback + self.client.send(IpcPacket::StopPlayback).unwrap(); + + Ok(()) + } + + fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> { + use zerocopy::AsBytes; + + if let AudioPacket::Samples(samples) = packet { + let samples_f32: &[f32] = &converter.f64_to_f32(&samples); + + let resampled = samplerate::convert( + 44100, + 48000, + 2, + samplerate::ConverterType::Linear, + &samples_f32, + ) + .unwrap(); + self.write_bytes(resampled.as_bytes())?; + } + + Ok(()) + } +} + +impl SinkAsBytes for StdoutSink { + fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> { + let get_buffer_len = || { + let buffer = self.buffer.lock().unwrap(); + buffer.len() + }; + + while get_buffer_len() > BUFFER_SIZE * 5 { + std::thread::sleep(Duration::from_millis(15)); + } + + let mut buffer = self.buffer.lock().unwrap(); + + buffer.extend_from_slice(data); + + Ok(()) + } +} diff --git a/src/audio/mod.rs b/src/audio/mod.rs new file mode 100644 index 0000000..fceb141 --- /dev/null +++ b/src/audio/mod.rs @@ -0,0 +1 @@ +pub mod backend; diff --git a/src/bot/commands/core/link.rs b/src/bot/commands/core/link.rs new file mode 100644 index 0000000..c4dd16f --- /dev/null +++ b/src/bot/commands/core/link.rs @@ -0,0 +1,115 @@ +use log::error; +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, + Result as SerenityResult, +}; + +use crate::{bot::commands::CommandOutput, database::Database}; + +pub const NAME: &str = "link"; + +async fn respond_message( + ctx: &Context, + command: &ApplicationCommandInteraction, + msg: impl Into, + ephemeral: bool, +) -> SerenityResult<()> { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content(msg.into()).ephemeral(ephemeral)) + }) + .await +} + +fn check_msg(result: SerenityResult<()>) { + if let Err(why) = result { + error!("Error sending message: {:?}", why); + } +} + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + let data = ctx.data.read().await; + let database = data.get::().unwrap(); + + if let Ok(_) = database.get_user_account(command.user.id.to_string()).await { + check_msg( + respond_message( + &ctx, + &command, + "You have already linked your Spotify account.", + true, + ) + .await, + ); + + return; + } + + if let Ok(request) = database.get_user_request(command.user.id.to_string()).await { + let base = std::env::var("SPOTICORD_ACCOUNTS_URL").unwrap(); + let link = format!("{}/spotify/{}", base, request.token); + + check_msg( + respond_message( + &ctx, + &command, + format!("Go to the following URL to link your account:\n{}", link), + true, + ) + .await, + ); + + return; + } + + match database + .create_user_request(command.user.id.to_string()) + .await + { + Ok(request) => { + let base = std::env::var("SPOTICORD_ACCOUNTS_URL").unwrap(); + let link = format!("{}/spotify/{}", base, request.token); + + check_msg( + respond_message( + &ctx, + &command, + format!("Go to the following URL to link your account:\n{}", link), + true, + ) + .await, + ); + + return; + } + Err(why) => { + error!("Error creating user request: {:?}", why); + + check_msg( + respond_message( + &ctx, + &command, + "An error occurred while serving your request. Please try again later.", + true, + ) + .await, + ); + + return; + } + }; + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name(NAME) + .description("Link your Spotify account to Spoticord") +} diff --git a/src/bot/commands/core/mod.rs b/src/bot/commands/core/mod.rs new file mode 100644 index 0000000..c48030d --- /dev/null +++ b/src/bot/commands/core/mod.rs @@ -0,0 +1,2 @@ +pub mod link; +pub mod unlink; diff --git a/src/bot/commands/core/unlink.rs b/src/bot/commands/core/unlink.rs new file mode 100644 index 0000000..c403354 --- /dev/null +++ b/src/bot/commands/core/unlink.rs @@ -0,0 +1,105 @@ +use log::error; +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, + Result as SerenityResult, +}; + +use crate::{ + bot::commands::CommandOutput, + database::{Database, DatabaseError}, + session::manager::SessionManager, +}; + +pub const NAME: &str = "unlink"; + +async fn respond_message( + ctx: &Context, + command: &ApplicationCommandInteraction, + msg: impl Into, + ephemeral: bool, +) -> SerenityResult<()> { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content(msg.into()).ephemeral(ephemeral)) + }) + .await +} + +fn check_msg(result: SerenityResult<()>) { + if let Err(why) = result { + error!("Error sending message: {:?}", why); + } +} + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + let data = ctx.data.read().await; + let database = data.get::().unwrap(); + let session_manager = data.get::().unwrap(); + + // Disconnect session if user has any + if let Some(session) = session_manager.find(command.user.id).await { + if let Err(why) = session.disconnect().await { + error!("Error disconnecting session: {:?}", why); + } + } + + // Check if user exists in the first place + if let Err(why) = database + .delete_user_account(command.user.id.to_string()) + .await + { + if let DatabaseError::InvalidStatusCode(status) = why { + if status == 404 { + check_msg( + respond_message( + &ctx, + &command, + "You cannot unlink your Spotify account if you currently don't have a linked Spotify account.", + true, + ) + .await, + ); + + return; + } + } + + error!("Error deleting user account: {:?}", why); + + check_msg( + respond_message( + &ctx, + &command, + "An unexpected error has occured while trying to unlink your account. Please try again later.", + true, + ) + .await, + ); + + return; + } + + check_msg( + respond_message( + &ctx, + &command, + "Successfully unlinked your Spotify account from Spoticord", + true, + ) + .await, + ); + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name(NAME) + .description("Unlink your Spotify account from Spoticord") +} diff --git a/src/bot/commands/mod.rs b/src/bot/commands/mod.rs new file mode 100644 index 0000000..3a00d57 --- /dev/null +++ b/src/bot/commands/mod.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, future::Future, pin::Pin}; + +use log::{debug, error}; +use serenity::{ + builder::{CreateApplicationCommand, CreateApplicationCommands}, + model::application::command::Command, + model::prelude::{ + interaction::{application_command::ApplicationCommandInteraction, InteractionResponseType}, + GuildId, + }, + prelude::{Context, TypeMapKey}, +}; + +mod core; +mod music; + +mod ping; +mod token; + +pub type CommandOutput = Pin + Send>>; +pub type CommandExecutor = fn(Context, ApplicationCommandInteraction) -> CommandOutput; + +pub struct CommandManager { + commands: HashMap, +} + +pub struct CommandInfo { + pub name: String, + pub executor: CommandExecutor, + pub register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand, +} + +impl CommandManager { + pub fn new() -> Self { + let mut instance = Self { + commands: HashMap::new(), + }; + + // Debug-only commands + #[cfg(debug_assertions)] + { + instance.insert_command(ping::NAME, ping::register, ping::run); + instance.insert_command(token::NAME, token::register, token::run); + } + + // Core commands + instance.insert_command(core::link::NAME, core::link::register, core::link::run); + instance.insert_command( + core::unlink::NAME, + core::unlink::register, + core::unlink::run, + ); + + // Music commands + instance.insert_command(music::join::NAME, music::join::register, music::join::run); + instance.insert_command( + music::leave::NAME, + music::leave::register, + music::leave::run, + ); + + instance + } + + pub fn insert_command( + &mut self, + name: impl Into, + register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand, + executor: CommandExecutor, + ) { + let name = name.into(); + + self.commands.insert( + name.clone(), + CommandInfo { + name, + register, + executor, + }, + ); + } + + pub async fn register_commands(&self, ctx: &Context) { + let cmds = &self.commands; + + debug!( + "Registering {} command{}", + cmds.len(), + if cmds.len() == 1 { "" } else { "s" } + ); + + fn _register_commands<'a>( + cmds: &HashMap, + mut commands: &'a mut CreateApplicationCommands, + ) -> &'a mut CreateApplicationCommands { + for cmd in cmds { + commands = commands.create_application_command(|command| (cmd.1.register)(command)); + } + + commands + } + + if let Ok(guild_id) = std::env::var("GUILD_ID") { + if let Ok(guild_id) = guild_id.parse::() { + let guild_id = GuildId(guild_id); + guild_id + .set_application_commands(&ctx.http, |command| _register_commands(cmds, command)) + .await + .expect("Failed to create guild commands"); + + return; + } + } + + Command::set_global_application_commands(&ctx.http, |command| { + _register_commands(cmds, command) + }) + .await + .expect("Failed to create global commands"); + } + + pub async fn execute_command(&self, ctx: &Context, interaction: ApplicationCommandInteraction) { + let command = self.commands.get(&interaction.data.name); + + if let Some(command) = command { + (command.executor)(ctx.clone(), interaction.clone()).await; + } else { + // Command does not exist + if let Err(why) = interaction + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + message + .content("Woops, that command doesn't exist") + .ephemeral(true) + }) + }) + .await + { + error!("Failed to respond to command: {}", why); + } + } + } +} + +impl TypeMapKey for CommandManager { + type Value = CommandManager; +} diff --git a/src/bot/commands/music/join.rs b/src/bot/commands/music/join.rs new file mode 100644 index 0000000..1009eb1 --- /dev/null +++ b/src/bot/commands/music/join.rs @@ -0,0 +1,141 @@ +use log::error; +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, + Result as SerenityResult, +}; + +use crate::{ + bot::commands::CommandOutput, + session::manager::{SessionCreateError, SessionManager}, +}; + +pub const NAME: &str = "join"; + +async fn respond_message( + ctx: &Context, + command: &ApplicationCommandInteraction, + msg: impl Into, + ephemeral: bool, +) -> SerenityResult<()> { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content(msg.into()).ephemeral(ephemeral)) + }) + .await +} + +fn check_msg(result: SerenityResult<()>) { + if let Err(why) = result { + error!("Error sending message: {:?}", why); + } +} + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + let guild = ctx.cache.guild(command.guild_id.unwrap()).unwrap(); + + // Get the voice channel id of the calling user + let channel_id = match guild + .voice_states + .get(&command.user.id) + .and_then(|state| state.channel_id) + { + Some(channel_id) => channel_id, + None => { + check_msg( + respond_message( + &ctx, + &command, + "You need to connect to a voice channel", + true, + ) + .await, + ); + + return; + } + }; + + let data = ctx.data.read().await; + let mut session_manager = data.get::().unwrap().clone(); + + // Check if another session is already active in this server + if let Some(session) = session_manager.get_session(guild.id).await { + let msg = if session.get_owner() == command.user.id { + "You are already playing music in this server" + } else { + "Someone else is already playing music in this server" + }; + + check_msg(respond_message(&ctx, &command, msg, true).await); + + return; + }; + + // Prevent duplicate Spotify sessions + if let Some(session) = session_manager.find(command.user.id).await { + check_msg( + respond_message( + &ctx, + &command, + format!( + "You are already playing music in another server ({}).\nStop playing in that server first before joining this one.", + ctx.cache.guild(session.get_guild_id()).unwrap().name + ), + true, + ) + .await, + ); + + return; + } + + // Create the session, and handle potential errors + if let Err(why) = session_manager + .create_session(&ctx, guild.id, channel_id, command.user.id) + .await + { + // Need to link first + if let SessionCreateError::NoSpotifyError = why { + check_msg( + respond_message( + &ctx, + &command, + "You need to link your Spotify account. Use `/link` or go to https://account.spoticord.com/ to get started.", + true, + ) + .await, + ); + + return; + } + + // Any other error + check_msg( + respond_message( + &ctx, + &command, + "An error occurred while joining the channel. Please try again later.", + true, + ) + .await, + ); + + return; + }; + + check_msg(respond_message(&ctx, &command, "Joined the voice channel.", false).await); + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name(NAME) + .description("Request the bot to join the current voice channel") +} diff --git a/src/bot/commands/music/leave.rs b/src/bot/commands/music/leave.rs new file mode 100644 index 0000000..de11b65 --- /dev/null +++ b/src/bot/commands/music/leave.rs @@ -0,0 +1,86 @@ +use log::error; +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, + Result as SerenityResult, +}; + +use crate::{bot::commands::CommandOutput, session::manager::SessionManager}; + +pub const NAME: &str = "leave"; + +async fn respond_message( + ctx: &Context, + command: &ApplicationCommandInteraction, + msg: &str, + ephemeral: bool, +) -> SerenityResult<()> { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content(msg).ephemeral(ephemeral)) + }) + .await +} + +fn check_msg(result: SerenityResult<()>) { + if let Err(why) = result { + error!("Error sending message: {:?}", why); + } +} + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + let data = ctx.data.read().await; + let session_manager = data.get::().unwrap().clone(); + + let session = match session_manager.get_session(command.guild_id.unwrap()).await { + Some(session) => session, + None => { + check_msg( + respond_message( + &ctx, + &command, + "I'm currently not connected to any voice channel", + true, + ) + .await, + ); + return; + } + }; + + if session.get_owner() != command.user.id { + // This message was generated by AI, and I love it. + check_msg(respond_message(&ctx, &command, "You are not the one who summoned me", true).await); + return; + }; + + if let Err(why) = session.disconnect().await { + error!("Error disconnecting from voice channel: {:?}", why); + + check_msg( + respond_message( + &ctx, + &command, + "An error occurred while disconnecting from the voice channel", + true, + ) + .await, + ); + return; + } + + check_msg(respond_message(&ctx, &command, "Successfully left the voice channel", false).await); + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name(NAME) + .description("Request the bot to leave the current voice channel") +} diff --git a/src/bot/commands/music/mod.rs b/src/bot/commands/music/mod.rs new file mode 100644 index 0000000..2c0fcca --- /dev/null +++ b/src/bot/commands/music/mod.rs @@ -0,0 +1,2 @@ +pub mod join; +pub mod leave; diff --git a/src/bot/commands/ping.rs b/src/bot/commands/ping.rs new file mode 100644 index 0000000..39724cc --- /dev/null +++ b/src/bot/commands/ping.rs @@ -0,0 +1,33 @@ +use log::info; +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, +}; + +use super::CommandOutput; + +pub const NAME: &str = "ping"; + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + info!("Pong!"); + + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content("Pong!")) + }) + .await + .unwrap(); + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("ping") + .description("Check if the bot is alive") +} diff --git a/src/bot/commands/token.rs b/src/bot/commands/token.rs new file mode 100644 index 0000000..bc78216 --- /dev/null +++ b/src/bot/commands/token.rs @@ -0,0 +1,42 @@ +use serenity::{ + builder::CreateApplicationCommand, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, +}; + +use crate::database::Database; + +use super::CommandOutput; + +pub const NAME: &str = "token"; + +pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput { + Box::pin(async move { + let data = ctx.data.read().await; + let db = data.get::().unwrap(); + + let token = db.get_access_token(command.user.id.to_string()).await; + + let content = match token { + Ok(token) => format!("Your token is: {}", token), + Err(why) => format!("You don't have a token yet. (Real: {})", why), + }; + + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| message.content(content).ephemeral(true)) + }) + .await + .unwrap(); + }) +} + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("token") + .description("Get your Spotify access token") +} diff --git a/src/bot/events.rs b/src/bot/events.rs new file mode 100644 index 0000000..6292061 --- /dev/null +++ b/src/bot/events.rs @@ -0,0 +1,61 @@ +/* This file implements all events for the Discord gateway */ + +use log::*; +use serenity::{ + async_trait, + model::prelude::{interaction::Interaction, Ready}, + prelude::{Context, EventHandler}, +}; + +use super::commands::CommandManager; + +// Handler struct with a command parameter, an array of dictionary which takes a string and function +pub struct Handler; + +#[async_trait] +impl EventHandler for Handler { + // READY event, emitted when the bot/shard starts up + async fn ready(&self, ctx: Context, ready: Ready) { + let data = ctx.data.read().await; + let command_manager = data.get::().unwrap(); + + debug!("Ready received, logged in as {}", ready.user.name); + + command_manager.register_commands(&ctx).await; + + info!("{} has come online", ready.user.name); + } + + // INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.) + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + if let Interaction::ApplicationCommand(command) = interaction { + // Commands must only be executed inside of guilds + if command.guild_id.is_none() { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(serenity::model::prelude::interaction::InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + message.content("This command can only be used in a server") + }) + }) + .await + .unwrap(); + + return; + } + + trace!( + "Received command interaction: command={} user={} guild={}", + command.data.name, + command.user.id, + command.guild_id.unwrap() + ); + + let data = ctx.data.read().await; + let command_manager = data.get::().unwrap(); + + command_manager.execute_command(&ctx, command).await; + } + } +} diff --git a/src/bot/mod.rs b/src/bot/mod.rs new file mode 100644 index 0000000..2bddc4f --- /dev/null +++ b/src/bot/mod.rs @@ -0,0 +1,2 @@ +pub mod commands; +pub mod events; diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..5b50588 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,269 @@ +use thiserror::Error; + +use log::trace; +use reqwest::{header::HeaderMap, Client, Error, Response, StatusCode}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::{json, Value}; +use serenity::prelude::TypeMapKey; + +use crate::utils; + +#[derive(Debug, Error)] +pub enum DatabaseError { + #[error("An error has occured during an I/O operation: {0}")] + IOError(String), + + #[error("An error has occured during a parsing operation: {0}")] + ParseError(String), + + #[error("An invalid status code was returned from a request: {0}")] + InvalidStatusCode(StatusCode), +} + +#[derive(Serialize, Deserialize)] +struct GetAccessTokenResponse { + id: String, + access_token: String, +} + +#[derive(Deserialize)] +pub struct User { + pub id: String, + pub device_name: String, + pub request: Option, + pub accounts: Vec, +} + +#[derive(Deserialize)] +pub struct Account { + pub user_id: String, + pub r#type: String, + pub access_token: String, + pub refresh_token: String, + pub expires: u64, +} + +#[derive(Deserialize)] +pub struct Request { + pub token: String, + pub user_id: String, + pub expires: u64, +} + +pub struct Database { + base_url: String, + default_headers: Option, +} + +// Request options +#[derive(Debug, Clone)] +struct RequestOptions { + pub method: Method, + pub path: String, + pub body: Option, + pub headers: Option, +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +enum Body { + Json(Value), + Text(String), +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +enum Method { + Get, + Post, + Put, + Delete, +} + +impl Database { + pub fn new(base_url: impl Into, default_headers: Option) -> Self { + Self { + base_url: base_url.into(), + default_headers, + } + } + + async fn request(&self, options: RequestOptions) -> Result { + let builder = Client::builder(); + let mut headers: HeaderMap = HeaderMap::new(); + let mut url = self.base_url.clone(); + + url.push_str(&options.path); + + if let Some(default_headers) = &self.default_headers { + headers.extend(default_headers.clone()); + } + + if let Some(request_headers) = options.headers { + headers.extend(request_headers); + } + + trace!("Requesting {} with headers: {:?}", url, headers); + + let client = builder.default_headers(headers).build()?; + + let mut request = match options.method { + Method::Get => client.get(url), + Method::Post => client.post(url), + Method::Put => client.put(url), + Method::Delete => client.delete(url), + }; + + request = if let Some(body) = options.body { + match body { + Body::Json(json) => request.json(&json), + Body::Text(text) => request.body(text), + } + } else { + request + }; + + let response = request.send().await?; + + Ok(response) + } + + async fn simple_get( + &self, + path: impl Into, + ) -> Result { + let response = match self + .request(RequestOptions { + method: Method::Get, + path: path.into(), + body: None, + headers: None, + }) + .await + { + Ok(response) => response, + Err(error) => return Err(DatabaseError::IOError(error.to_string())), + }; + + match response.status() { + StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => {} + status => return Err(DatabaseError::InvalidStatusCode(status)), + }; + + let body = match response.json::().await { + Ok(body) => body, + Err(error) => return Err(DatabaseError::ParseError(error.to_string())), + }; + + Ok(body) + } +} + +impl Database { + pub async fn get_user(&self, user_id: impl Into) -> Result { + let path = format!("/user/{}", user_id.into()); + + self.simple_get(path).await + } + + // Get the Spotify access token for a user + pub async fn get_access_token( + &self, + user_id: impl Into + Send, + ) -> Result { + let body: GetAccessTokenResponse = self + .simple_get(format!("/user/{}/spotify/access_token", user_id.into())) + .await?; + + Ok(body.access_token) + } + + // Get the Spotify account for a user + pub async fn get_user_account( + &self, + user_id: impl Into + Send, + ) -> Result { + let body: Account = self + .simple_get(format!("/account/{}/spotify", user_id.into())) + .await?; + + Ok(body) + } + + // Get the Request for a user + pub async fn get_user_request( + &self, + user_id: impl Into + Send, + ) -> Result { + let body: Request = self + .simple_get(format!("/request/by-user/{}", user_id.into())) + .await?; + + Ok(body) + } + + // Create the link Request for a user + pub async fn create_user_request( + &self, + user_id: impl Into + Send, + ) -> Result { + let body = json!({ + "user_id": user_id.into(), + "expires": utils::get_time() + (1000 * 60 * 60) + }); + + let response = match self + .request(RequestOptions { + method: Method::Post, + path: "/request".into(), + body: Some(Body::Json(body)), + headers: None, + }) + .await + { + Ok(response) => response, + Err(err) => return Err(DatabaseError::IOError(err.to_string())), + }; + + match response.status() { + StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => {} + status => return Err(DatabaseError::InvalidStatusCode(status)), + }; + + let body = match response.json::().await { + Ok(body) => body, + Err(error) => return Err(DatabaseError::ParseError(error.to_string())), + }; + + Ok(body) + } + + pub async fn delete_user_account( + &self, + user_id: impl Into + Send, + ) -> Result<(), DatabaseError> { + let response = match self + .request(RequestOptions { + method: Method::Delete, + path: format!("/account/{}/spotify", user_id.into()), + body: None, + headers: None, + }) + .await + { + Ok(response) => response, + Err(err) => return Err(DatabaseError::IOError(err.to_string())), + }; + + match response.status() { + StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => {} + status => return Err(DatabaseError::InvalidStatusCode(status)), + }; + + Ok(()) + } +} + +impl TypeMapKey for Database { + type Value = Database; +} diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs new file mode 100644 index 0000000..57e824c --- /dev/null +++ b/src/ipc/mod.rs @@ -0,0 +1,69 @@ +use std::sync::{Arc, Mutex}; + +use ipc_channel::ipc::{self, IpcError, IpcOneShotServer, IpcReceiver, IpcSender}; + +use self::packet::IpcPacket; + +pub mod packet; + +pub struct Server { + tx: IpcOneShotServer>, + rx: IpcOneShotServer>, +} + +impl Server { + pub fn create() -> Result<(Self, String, String), IpcError> { + let (tx, tx_name) = IpcOneShotServer::new().map_err(IpcError::Io)?; + let (rx, rx_name) = IpcOneShotServer::new().map_err(IpcError::Io)?; + + Ok((Self { tx, rx }, tx_name, rx_name)) + } + + pub fn accept(self) -> Result { + let (_, tx) = self.tx.accept().map_err(IpcError::Bincode)?; + let (_, rx) = self.rx.accept().map_err(IpcError::Bincode)?; + + Ok(Client::new(tx, rx)) + } +} + +#[derive(Clone)] +pub struct Client { + tx: Arc>>, + rx: Arc>>, +} + +impl Client { + pub fn new(tx: IpcSender, rx: IpcReceiver) -> Client { + Client { + tx: Arc::new(Mutex::new(tx)), + rx: Arc::new(Mutex::new(rx)), + } + } + + pub fn connect(tx_name: impl Into, rx_name: impl Into) -> Result { + let (tx, remote_rx) = ipc::channel().map_err(IpcError::Io)?; + let (remote_tx, rx) = ipc::channel().map_err(IpcError::Io)?; + + let ttx = IpcSender::connect(tx_name.into()).map_err(IpcError::Io)?; + let trx = IpcSender::connect(rx_name.into()).map_err(IpcError::Io)?; + + ttx.send(remote_tx).map_err(IpcError::Bincode)?; + trx.send(remote_rx).map_err(IpcError::Bincode)?; + + Ok(Client::new(tx, rx)) + } + + pub fn send(&self, packet: IpcPacket) -> Result<(), IpcError> { + self + .tx + .lock() + .unwrap() + .send(packet) + .map_err(IpcError::Bincode) + } + + pub fn recv(&self) -> Result { + self.rx.lock().unwrap().recv() + } +} diff --git a/src/ipc/packet.rs b/src/ipc/packet.rs new file mode 100644 index 0000000..84421b6 --- /dev/null +++ b/src/ipc/packet.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum IpcPacket { + Quit, + + Connect(String, String), + Disconnect, + + StartPlayback, + StopPlayback, +} diff --git a/src/librespot_ext/discovery.rs b/src/librespot_ext/discovery.rs new file mode 100644 index 0000000..a048cd0 --- /dev/null +++ b/src/librespot_ext/discovery.rs @@ -0,0 +1,18 @@ +use librespot::discovery::Credentials; +use librespot::protocol::authentication::AuthenticationType; + +pub trait CredentialsExt { + fn with_token(username: impl Into, token: impl Into) -> Credentials; +} + +impl CredentialsExt for Credentials { + // Enable the use of a token to connect to Spotify + // Wouldn't want to ask users for their password would we? + fn with_token(username: impl Into, token: impl Into) -> Credentials { + Credentials { + username: username.into(), + auth_type: AuthenticationType::AUTHENTICATION_SPOTIFY_TOKEN, + auth_data: token.into().into_bytes(), + } + } +} diff --git a/src/librespot_ext/mod.rs b/src/librespot_ext/mod.rs new file mode 100644 index 0000000..018c2b5 --- /dev/null +++ b/src/librespot_ext/mod.rs @@ -0,0 +1,6 @@ +// Librespot extensions +// ============================= +// Librespot is missing some key features/functionality for Spoticord to work properly. +// This module contains the extensions to librespot that are required for Spoticord to work. + +pub mod discovery; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cc16721 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,87 @@ +use chrono::Datelike; +use dotenv::dotenv; + +use log::*; +use serenity::{framework::StandardFramework, prelude::GatewayIntents, Client}; +use songbird::SerenityInit; +use std::env; + +use crate::{bot::commands::CommandManager, database::Database, session::manager::SessionManager}; + +mod audio; +mod bot; +mod database; +mod ipc; +mod librespot_ext; +mod player; +mod session; +mod utils; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let args: Vec = env::args().collect(); + + if args.len() > 2 { + if &args[1] == "--player" { + // Woah! We're running in player mode! + + debug!("Starting Spoticord player"); + + player::main().await; + + return; + } + } + + info!("It's a good day"); + info!(" - Spoticord {}", chrono::Utc::now().year()); + + let result = dotenv(); + + if let Ok(path) = result { + debug!("Loaded environment file: {}", path.to_str().unwrap()); + } else { + warn!("No .env file found, expecting all necessary environment variables"); + } + + let token = env::var("TOKEN").expect("a token in the environment"); + let db_url = env::var("DATABASE_URL").expect("a database URL in the environment"); + + // Create client + let mut client = Client::builder( + token, + GatewayIntents::GUILDS | GatewayIntents::GUILD_VOICE_STATES, + ) + .event_handler(crate::bot::events::Handler) + .framework(StandardFramework::new()) + .register_songbird() + .await + .unwrap(); + + { + let mut data = client.data.write().await; + + data.insert::(Database::new(db_url, None)); + data.insert::(CommandManager::new()); + data.insert::(SessionManager::new()); + } + + let shard_manager = client.shard_manager.clone(); + + // Spawn a task to shutdown the bot when a SIGINT is received + tokio::spawn(async move { + tokio::signal::ctrl_c() + .await + .expect("Could not register CTRL+C handler"); + + info!("SIGINT Received, shutting down..."); + + shard_manager.lock().await.shutdown_all().await; + }); + + if let Err(why) = client.start_autosharded().await { + println!("Error in bot: {:?}", why); + } +} diff --git a/src/player/mod.rs b/src/player/mod.rs new file mode 100644 index 0000000..6a1398b --- /dev/null +++ b/src/player/mod.rs @@ -0,0 +1,190 @@ +use librespot::{ + connect::spirc::Spirc, + core::{ + config::{ConnectConfig, SessionConfig}, + session::Session, + }, + discovery::Credentials, + playback::{ + config::{Bitrate, PlayerConfig}, + mixer::{self, MixerConfig}, + player::Player, + }, +}; +use log::{debug, error, info, trace, warn}; +use serde_json::json; + +use crate::{ + audio::backend::StdoutSink, + ipc::{self, packet::IpcPacket}, + librespot_ext::discovery::CredentialsExt, + utils, +}; + +pub struct SpoticordPlayer { + client: ipc::Client, + session: Option, +} + +impl SpoticordPlayer { + pub fn create(client: ipc::Client) -> Self { + Self { + client, + session: None, + } + } + + pub async fn start(&mut self, token: impl Into, device_name: impl Into) { + let token = token.into(); + + // Get the username (required for librespot) + let username = utils::spotify::get_username(&token).await.unwrap(); + + let session_config = SessionConfig::default(); + let player_config = PlayerConfig { + bitrate: Bitrate::Bitrate96, + ..PlayerConfig::default() + }; + + // Log in using the token + let credentials = Credentials::with_token(username, &token); + + // Connect the session + let (session, _) = match Session::connect(session_config, credentials, None, false).await { + Ok((session, credentials)) => (session, credentials), + Err(why) => panic!("Failed to connect: {}", why), + }; + + // Store session for later use + self.session = Some(session.clone()); + + // Volume mixer + let mixer = (mixer::find(Some("softvol")).unwrap())(MixerConfig::default()); + + let client = self.client.clone(); + + // Create the player + let (player, _) = Player::new( + player_config, + session.clone(), + mixer.get_soft_volume(), + move || Box::new(StdoutSink::new(client)), + ); + + let mut receiver = player.get_player_event_channel(); + + let (_, spirc_run) = Spirc::new( + ConnectConfig { + name: device_name.into(), + initial_volume: Some(65535), + ..ConnectConfig::default() + }, + session.clone(), + player, + mixer, + ); + + let device_id = session.device_id().to_owned(); + + // IPC Handler + tokio::spawn(async move { + let client = reqwest::Client::new(); + + // Try to switch to the device + loop { + match client + .put("https://api.spotify.com/v1/me/player") + .bearer_auth(token.clone()) + .json(&json!({ + "device_ids": [device_id], + })) + .send() + .await + { + Ok(resp) => { + if resp.status() == 202 { + info!("Successfully switched to device"); + break; + } + } + Err(why) => { + debug!("Failed to set device: {}", why); + break; + } + } + + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + // TODO: Do IPC stuff with these events + loop { + let event = match receiver.recv().await { + Some(event) => event, + None => break, + }; + + trace!("Player event: {:?}", event); + } + + info!("Player stopped"); + }); + + tokio::spawn(spirc_run); + } + + pub fn stop(&mut self) { + if let Some(session) = self.session.take() { + session.shutdown(); + } + } +} + +pub async fn main() { + let args = std::env::args().collect::>(); + + let tx_name = args[2].clone(); + let rx_name = args[3].clone(); + + // Create IPC communication channel + let client = ipc::Client::connect(tx_name, rx_name).expect("Failed to connect to IPC"); + + // Create the player + let mut player = SpoticordPlayer::create(client.clone()); + + loop { + let message = match client.recv() { + Ok(message) => message, + Err(why) => { + error!("Failed to receive message: {}", why); + break; + } + }; + + match message { + IpcPacket::Connect(token, device_name) => { + info!("Connecting to Spotify with device name {}", device_name); + + player.start(token, device_name).await; + } + + IpcPacket::Disconnect => { + info!("Disconnecting from Spotify"); + + player.stop(); + } + + IpcPacket::Quit => { + debug!("Received quit packet, exiting"); + + player.stop(); + break; + } + + _ => { + warn!("Received unknown packet: {:?}", message); + } + } + } + + info!("We're done here, shutting down..."); +} diff --git a/src/session/manager.rs b/src/session/manager.rs new file mode 100644 index 0000000..b8d071c --- /dev/null +++ b/src/session/manager.rs @@ -0,0 +1,85 @@ +use std::{collections::HashMap, sync::Arc}; + +use serenity::{ + model::prelude::{ChannelId, GuildId, UserId}, + prelude::{Context, TypeMapKey}, +}; +use thiserror::Error; + +use super::SpoticordSession; + +#[derive(Debug, Error)] +pub enum SessionCreateError { + #[error("The user has not linked their Spotify account")] + NoSpotifyError, + + #[error("An error has occured while communicating with the database")] + DatabaseError, + + #[error("Failed to join voice channel {0} ({1})")] + JoinError(ChannelId, GuildId), + + #[error("Failed to start player process")] + ForkError, +} + +#[derive(Clone)] +pub struct SessionManager { + sessions: Arc>>>, + owner_map: Arc>>, +} + +impl TypeMapKey for SessionManager { + type Value = SessionManager; +} + +impl SessionManager { + pub fn new() -> SessionManager { + SessionManager { + sessions: Arc::new(tokio::sync::RwLock::new(HashMap::new())), + owner_map: Arc::new(tokio::sync::RwLock::new(HashMap::new())), + } + } + + /// Creates a new session for the given user in the given guild. + pub async fn create_session( + &mut self, + ctx: &Context, + guild_id: GuildId, + channel_id: ChannelId, + owner_id: UserId, + ) -> Result<(), SessionCreateError> { + let mut sessions = self.sessions.write().await; + let mut owner_map = self.owner_map.write().await; + + let session = SpoticordSession::new(ctx, guild_id, channel_id, owner_id).await?; + + sessions.insert(guild_id, Arc::new(session)); + owner_map.insert(owner_id, guild_id); + + Ok(()) + } + + /// Remove (and destroy) a session + pub async fn remove_session(&mut self, guild_id: GuildId) { + let mut sessions = self.sessions.write().await; + sessions.remove(&guild_id); + } + + /// Get a session by its guild ID + pub async fn get_session(&self, guild_id: GuildId) -> Option> { + let sessions = self.sessions.read().await; + + sessions.get(&guild_id).cloned() + } + + /// Find a Spoticord session by their current owner's ID + pub async fn find(&self, owner_id: UserId) -> Option> { + let sessions = self.sessions.read().await; + let owner_map = self.owner_map.read().await; + + let guild_id = owner_map.get(&owner_id)?; + + sessions.get(&guild_id).cloned() + } +} diff --git a/src/session/mod.rs b/src/session/mod.rs new file mode 100644 index 0000000..bb0d9cc --- /dev/null +++ b/src/session/mod.rs @@ -0,0 +1,241 @@ +use self::manager::{SessionCreateError, SessionManager}; +use crate::{ + database::{Database, DatabaseError}, + ipc::{self, packet::IpcPacket}, +}; +use ipc_channel::ipc::IpcError; +use log::*; +use serenity::{ + async_trait, + model::prelude::{ChannelId, GuildId, UserId}, + prelude::Context, +}; +use songbird::{ + create_player, + error::JoinResult, + input::{children_to_reader, Input}, + tracks::TrackHandle, + Call, Event, EventContext, EventHandler, +}; +use std::{ + process::{Command, Stdio}, + sync::Arc, +}; +use tokio::sync::Mutex; + +pub mod manager; + +#[derive(Clone)] +pub struct SpoticordSession { + owner: UserId, + guild_id: GuildId, + channel_id: ChannelId, + + session_manager: SessionManager, + + call: Arc>, + track: TrackHandle, +} + +impl SpoticordSession { + pub async fn new( + ctx: &Context, + guild_id: GuildId, + channel_id: ChannelId, + owner_id: UserId, + ) -> Result { + // Get the Spotify token of the owner + let data = ctx.data.read().await; + let database = data.get::().unwrap(); + let session_manager = data.get::().unwrap().clone(); + + let token = match database.get_access_token(owner_id.to_string()).await { + Ok(token) => token, + Err(why) => { + if let DatabaseError::InvalidStatusCode(code) = why { + if code == 404 { + return Err(SessionCreateError::NoSpotifyError); + } + } + + return Err(SessionCreateError::DatabaseError); + } + }; + + let user = match database.get_user(owner_id.to_string()).await { + Ok(user) => user, + Err(why) => { + error!("Failed to get user: {:?}", why); + return Err(SessionCreateError::DatabaseError); + } + }; + + // Create IPC oneshot server + let (server, tx_name, rx_name) = match ipc::Server::create() { + Ok(server) => server, + Err(why) => { + error!("Failed to create IPC server: {:?}", why); + return Err(SessionCreateError::ForkError); + } + }; + + // Join the voice channel + let songbird = songbird::get(ctx).await.unwrap().clone(); + + let (call, result) = songbird.join(guild_id, channel_id).await; + + if let Err(why) = result { + error!("Error joining voice channel: {:?}", why); + return Err(SessionCreateError::JoinError(channel_id, guild_id)); + } + + let mut call_mut = call.lock().await; + + // Spawn player process + let args: Vec = std::env::args().collect(); + let child = match Command::new(&args[0]) + .args(["--player", &tx_name, &rx_name]) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + { + Ok(child) => child, + Err(why) => { + error!("Failed to start player process: {:?}", why); + return Err(SessionCreateError::ForkError); + } + }; + + // Establish bi-directional IPC channel + let client = match server.accept() { + Ok(client) => client, + Err(why) => { + error!("Failed to accept IPC connection: {:?}", why); + + return Err(SessionCreateError::ForkError); + } + }; + + // Pipe player audio to the voice channel + let reader = children_to_reader::(vec![child]); + + // Create track (paused, fixes audio glitches) + let (mut track, track_handle) = create_player(Input::float_pcm(true, reader)); + track.pause(); + + // Set call audio to track + call_mut.play_only(track); + + // Clone variables for use in the IPC handler + let ipc_track = track_handle.clone(); + let ipc_client = client.clone(); + + // Handle IPC packets + // This will automatically quit once the IPC connection is closed + tokio::spawn(async move { + let check_result = |result| { + if let Err(why) = result { + error!("Failed to issue track command: {:?}", why); + } + }; + + loop { + let msg = match ipc_client.recv() { + Ok(msg) => msg, + Err(why) => { + if let IpcError::Disconnected = why { + break; + } + + error!("Failed to receive IPC message: {:?}", why); + break; + } + }; + + match msg { + IpcPacket::StartPlayback => { + check_result(ipc_track.play()); + } + + IpcPacket::StopPlayback => { + check_result(ipc_track.pause()); + } + + _ => {} + } + } + }); + + // Set up events + let instance = Self { + owner: owner_id, + guild_id, + channel_id, + session_manager, + call: call.clone(), + track: track_handle, + }; + + call_mut.add_global_event( + songbird::Event::Core(songbird::CoreEvent::DriverDisconnect), + instance.clone(), + ); + + call_mut.add_global_event( + songbird::Event::Core(songbird::CoreEvent::ClientDisconnect), + instance.clone(), + ); + + if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) { + error!("Failed to send IpcPacket::Connect packet: {:?}", why); + } + + Ok(instance) + } + + pub async fn disconnect(&self) -> JoinResult<()> { + info!("Disconnecting from voice channel {}", self.channel_id); + + self + .session_manager + .clone() + .remove_session(self.guild_id) + .await; + + let mut call = self.call.lock().await; + + self.track.stop().unwrap_or(()); + call.remove_all_global_events(); + call.leave().await + } + + pub fn get_owner(&self) -> UserId { + self.owner + } + + pub fn get_guild_id(&self) -> GuildId { + self.guild_id + } + + pub fn get_channel_id(&self) -> ChannelId { + self.channel_id + } +} + +#[async_trait] +impl EventHandler for SpoticordSession { + async fn act(&self, ctx: &EventContext<'_>) -> Option { + match ctx { + EventContext::DriverDisconnect(_) => { + debug!("Driver disconnected, leaving voice channel"); + self.disconnect().await.ok(); + } + EventContext::ClientDisconnect(who) => { + debug!("Client disconnected, {}", who.user_id.to_string()); + } + _ => {} + } + + return None; + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..71ac53d --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,10 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +pub mod spotify; + +pub fn get_time() -> u64 { + let now = SystemTime::now(); + let since_the_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards"); + + since_the_epoch.as_secs() +} diff --git a/src/utils/spotify.rs b/src/utils/spotify.rs new file mode 100644 index 0000000..e3b98b3 --- /dev/null +++ b/src/utils/spotify.rs @@ -0,0 +1,36 @@ +use log::{error, trace}; +use serde_json::Value; + +pub async fn get_username(token: impl Into) -> Result { + let token = token.into(); + let client = reqwest::Client::new(); + + let response = match client + .get("https://api.spotify.com/v1/me") + .bearer_auth(token) + .send() + .await + { + Ok(response) => response, + Err(why) => { + error!("Failed to get username: {}", why); + return Err(format!("{}", why)); + } + }; + + let body: Value = match response.json().await { + Ok(body) => body, + Err(why) => { + error!("Failed to parse body: {}", why); + return Err(format!("{}", why)); + } + }; + + if let Value::String(username) = &body["id"] { + trace!("Got username: {}", username); + return Ok(username.clone()); + } + + error!("Missing 'id' field in body"); + Err("Failed to parse body: Invalid body received".to_string()) +}