diff options
| author | Mishio595 <[email protected]> | 2018-08-12 13:02:49 -0600 |
|---|---|---|
| committer | Mishio595 <[email protected]> | 2018-08-12 13:02:49 -0600 |
| commit | 517a56317a86d327a7ed990d555fbe871e39f0f1 (patch) | |
| tree | e867c2d13b11530d3e62655779c3442370bebd79 | |
| parent | correct a typo (diff) | |
| parent | Resolve conflicts (diff) | |
| download | serenity-517a56317a86d327a7ed990d555fbe871e39f0f1.tar.xz serenity-517a56317a86d327a7ed990d555fbe871e39f0f1.zip | |
Merge branch 'master' into old_message_on_update
82 files changed, 5290 insertions, 3598 deletions
diff --git a/.travis.yml b/.travis.yml index bd426a7..c3ea58c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ rust: - stable - beta - nightly + - 1.25.0 os: - linux - osx diff --git a/CHANGELOG.md b/CHANGELOG.md index 223f832..45e2037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,180 @@ All notable changes to this project will be documented in this file. This project mostly adheres to [Semantic Versioning][semver]. +## [0.5.7] - 2018-08-09 + +This is a hotfix release for an incorrect warning about cache deadlocking during +event dispatches in the client and fixing some routing method typos due to the +HTTP rewrite. + +Thanks to the following for their contributions: + +- [@acdenisSK] +- [@Lymia] +- [@zeyla] + +### Fixed + +- [client] Fix erroneous deadlock detection messages ([@Lymia]) [c:d1266fc] +- [http] Fix some routing issues ([@zeyla]) [c:04b410e] + +### Misc. + +- Slightly reword a cache update comment ([@acdenisSK]) [c:3a58090] + +## [0.5.6] - 2018-08-07 + +This is a bugfix release that fixes a long-standing bug causing shards to +randomly die under certain rare conditions when dispatching the Ready event, +and compilation of the `cache` and `client` features without the `framework` +feature. This also contains an internal rewrite of the HTTP module. + +The minimum required rustc version is now pinned at 1.25.0. + +Thanks to the following for their contributions: + +- [@acdenisSK] +- [@Erk-] +- [@Lakelezz] +- [@Mishio595] +- [@Roughsketch] +- [@zeyla] + +### Upgrade Path + +Per [c:01e3c33], `Context::edit_profile` has been deprecated. Call +`serenity::http::edit_profile` instead. + +### Added + +- [model] `impl AsRef<MessageId> for Message` ([@Mishio595]) [c:1de3937] +- [model] Add `From` impls for `Game`, genericify `Game` params ([@zeyla]) + [c:e1332a5], [c:a4c3fec] +- [http] Make `http::fire`, `http::request` public ([@zeyla]) [c:0d55363] +- [framework] Add no-parse getters and advancer to `Args` ([@acdenisSK]) + [c:73ab20f] +- [model] Add support for new `PRIORITY_SPEAKER` permission ([@Erk-]) + [c:2179623] + +### Fixed + +- [client] Don't delay Ready event with cache enabled ([@zeyla]) [c:12d5321] +- [framework] Handle no delimiters in `Args` ([@acdenisSK]) [c:e5ea6c1], + [c:9568e3b] +- [client] Add missing `mut`, fixing no-framework compilation ([@acdenisSK]) + [c:90c7ec4] +- [framework] Check if message is empty in `Args` ([@acdenisSK]) [c:0501020] +- [client] Fix potential cache deadlocking when dispatching ([@zeyla]) + [c:f064d65] +- [framework] Pass failed sub-command to default command ([@Lakelezz]) + [c:db21036] +- [framework] Fix default command upon shortcut prefix ([@Lakelezz]) [c:8f128b2] + +### Changed + +- [client] Deprecate `Context::edit_profile` ([@zeyla]) [c:01e3c33] + +### Misc. + +- [model] Fix `ChannelId::send_message`'s dead links ([@acdenisSK]) [c:7a93557] +- [model] Add note about cache in `UserId::get` docs ([@zeyla]) [c:e2873c8] +- [general] Reduce required rustc to 1.25.0 ([@zeyla]) [c:f3f22d7], [c:b324774] +- [model] Make `GuildId::member` use cache when possible ([@Roughsketch]) + [c:21eb42f] +- [framework] Reword some `StandardFramework::complex_bucket` docs + ([@acdenisSK]) [c:02de778] +- [framework] Internally refactor `positions` ([@acdenisSK]) [c:2a6c3b1] +- [framework] Update `Configuration` default value listings ([@zeyla]) + [c:602c5a7] +- [http] Maintain a single, re-used HTTP client ([@zeyla]) [c:8c0e5a3] +- [http] Redo the HTTP module internally ([@zeyla]) [c:a0b0dd2], [c:4648f58], + [c:8918201], [c:8301333], [c:bbbf638], [c:9a863bd], [c:c458099], [c:aa437d4] +- [docs] Don't return Result from tests ([@acdenisSK]) [c:e290b03] +- [docs] Fix all dead links in permissions ([@Erk-]) [c:869fff5] + +## [0.5.5] - 2018-07-25 + +This release is mostly a bugfix release. Thanks to the following for their +contributions: + +- [@acdenisSK] +- [@drklee3] +- [@foxbot] +- [@Lakelezz] +- [@Mishio595] +- [@perryprog] +- [@TheUnitedStatesOfAmerica] +- [@zeyla] + +### Added + +- [framework] Add `Args::rest` ([@acdenisSK]) [c:9b2cd75] +- [model] Add `Message::guild_id` structfield ([@foxbot], [@zeyla]) [c:a9e8626], + [c:3121f90] +- [framework] Improve logic for displaying help ([@Lakelezz]) [c:7937025] +- [http] Add `http::ratelimiting::offset` ([@zeyla]) [c:55555b8] +- [cache] Make the Cache Update API public ([@zeyla]) [c:9e56062] +- [utils] Add associated consts in `utils::Colour` ([@zeyla]) [c:bbfc8e2] +- [model] `impl From<&ID> for ID` for all Id types ([@zelya]) [c:9e45642], + [c:530ea76] +- [cache] Add a Message cache API ([@zeyla]) [c:e602630] +- [voice] Add `streamer::ffmpeg_optioned` ([@zeyla]) [c:5dab87b], [c:1f3a57e] +- [model] Implement Mentionable for `GuildChannel` ([@Mishio595]) [c:ce8da79] +- [framework] Allow nil prefixes in DMs ([@acdenisSK]) [c:10bbffe] +- [model] Implement `Mentionable` for `ChannelCategory`, `Group`, + `PrivateChannel` ([@zeyla]) [c:dd3744b], [c:8ce8234], [c:d11d916], [c:5abc7d1] +- [framework] Add checks for groups ([@Lakelezz]) [c:29480e5] +- [framework] Support multiple prefixes for command groups ([@Lakelezz]) + [c:305d200] +- [framework] Add default commands for command groups ([@Lakelezz]) [c:40c8248], + [c:8aefde0] + +### Fixed + +- [framework] Handle debug impls better ([@acdenisSK]) [c:caeab28], [c:7eac4d5] +- [framework] Reorder some dispatch checks to fix an owner override bug + ([@acdenisSK]) [c:8114a7a], [c:93f453b] +- [framework] Force `Args::find{,_n}` to be quote-aware ([@acdenisSK]) + [c:f0f06b7] +- [framework] Fix an `Args` test ([@zeyla]) [c:2ef660e] +- [framework] Fix command visibility on no help ([@Lakelezz]) [c:aeb89af] +- [framework] Add missing `Send + Sync` bounds on `Check` ([@acdenisSK]) + [c:f09b661] +- [utils] Fix `utils::is_nsfw` slicing ([@acdenisSK], [@zeyla]) [c:0067c33], + [c:ccd2506] +- [utils] Fix `nsfw-` case in `utils::is_nsfw` ([@zeyla]) [c:bd4aa0a] +- [framework] Don't assume all characters at end are 1-length ([@acdenisSK]) + [c:4e4dcb1] +- [framework] Don't suggest command if no command is related to input + ([@Lakelezz]) [c:614402f] + +### Changed + +- [model] Make `Invite::guild` and `RichInvite::guild` optional ([@zeyla]) + [c:3a647e3] + +### Misc. + +- [framework] Fix example typo ([@perryprog]) [c:d0d363f] +- [framework] Add more docs to `Args` ([@acdenisSK]) [c:04b0be1] +- [general] Fix extraneous spaces at the end of lines ([@zeyla]) [c:6ddfef8] +- [http] Add (late) april fool's functions ([@TheUnitedStatesOfAmerica]) + [c:5ffdcea] +- Rename https://github.com/serenity-rs/serenity/commit/6e1edde4a3fe27d0d90db7ea906ca5f115a2d5fb +- [framework] Remove some repitition repition ([@acdenisSK]) [c:10f7548], + [c:1ec1086] +- [docs] Add more docs to `CreateEmbed::fields` ([@acdenisSK]) [c:703d135] +- [docs] Remove some dead links ([@acdenisSK], [@Lakelezz]) [c:eae624e], + [c:4cf83d0] +- [docs] Remove old notice about `CreateEmbed::field` ([@acdenisSK]) [c:5b66ace] +- [examples] Add `CreateEmbed::field` and `CreateEmbed::fields` usage to example + 11 ([@drklee3]) [c:a9a2c27] +- [general] Monomorphize all functions ([@zeyla]) [c:7b9764c] +- [general] Update README logo URI ([@zeyla]) [c:2ff765b] +- [docs] Fix doc links with no anchor ([@zeyla]) [c:0d6e019] +- [docs] Add docs for `Args::new` ([@acdenisSK]) [c:b520ec7] +- [general] Fix some clippy lints ([@zeyla]) [c:9da7669] + ## [0.5.4] - 2018-06-07 Thanks to the following for their contributions: @@ -2076,6 +2250,9 @@ rest::get_guilds(GuildPagination::After(GuildId(777)), 50); Initial commit. +[0.5.7]: https://github.com/serenity-rs/serenity/compare/v0.5.6...v0.5.7 +[0.5.6]: https://github.com/serenity-rs/serenity/compare/v0.5.5...v0.5.6 +[0.5.5]: https://github.com/serenity-rs/serenity/compare/v0.5.4...v0.5.5 [0.5.4]: https://github.com/serenity-rs/serenity/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/serenity-rs/serenity/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/serenity-rs/serenity/compare/v0.5.1...v0.5.2 @@ -2113,6 +2290,7 @@ Initial commit. [@ConcurrentMarxistGC]: https://github.com/ConcurrentMarxistGC [@DeltaEvo]: https://github.com/DeltaEvo [@drklee3]: https://github.com/drklee3 +[@Erk-]: https://github.com/Erk- [@eLunate]: https://github.com/eLunate [@emoticon]: https://github.com/emoticon [@efyang]: https://github.com/efyang @@ -2133,8 +2311,10 @@ Initial commit. [@joek13]: https://github.com/joek13 [@Lakelezz]: https://github.com/Lakelezz [@lolzballs]: https://github.com/lolzballs +[@Lymia]: https://github.com/Lymia [@khazhyk]: https://github.com/khazhyk [@megumisonoda]: https://github.com/megumisonoda +[@Mishio595]: https://github.com/Mishio595 [@MOZGIII]: https://github.com/MOZGIII [@nabijaczleweli]: https://github.com/nabijaczleweli [@perryprog]: https://github.com/perryprog @@ -2144,12 +2324,105 @@ Initial commit. [@SunDwarf]: https://github.com/SunDwarf [@tahahawa]: https://github.com/tahahawa [@ThatsNoMoon]: https://github.com/ThatsNoMoon +[@TheUnitedStatesOfAmerica]: https://github.com/TheUnitedStatesOfAmerica [@thelearnerofcode]: https://github.com/thelearnerofcode [@timotree3]: https://github.com/timotree3 [@xentec]: https://github.com/xentec [@vityafx]: https://github.com/vityafx [@zeyla]: https://github.com/zeyla +[c:04b410e]: https://github.com/serenity-rs/serenity/commit/04b410ee75b2eb29f32e66fc137d3992a4972f1d +[c:3a58090]: https://github.com/serenity-rs/serenity/commit/3a580909c489c328f3faa10741debd4b063e7fbd +[c:d1266fc]: https://github.com/serenity-rs/serenity/commit/d1266fc3051a436f87a4778c5081c2228eb50b1c + +[c:01e3c33]: https://github.com/serenity-rs/serenity/commit/01e3c331ed188e2b95bafa2fa0fc63d5c0c03905 +[c:02de778]: https://github.com/serenity-rs/serenity/commit/02de7789d72141434264e8bd7cee7e1fc65a043f +[c:0501020]: https://github.com/serenity-rs/serenity/commit/05010204eaded91b29aef0561fc8fb668b522760 +[c:0d55363]: https://github.com/serenity-rs/serenity/commit/0d553630c1a9da216e42e7c0a9bedaccfedf678d +[c:12d5321]: https://github.com/serenity-rs/serenity/commit/12d53214f39211a4c02026d9389b9aa2bfa8a5ee +[c:1de3937]: https://github.com/serenity-rs/serenity/commit/1de39377a2e428f9652d887627f420349337c5b1 +[c:2179623]: https://github.com/serenity-rs/serenity/commit/2179623ebf12f7d8e16cc87e193ecd4de0f7b1fe +[c:21eb42f]: https://github.com/serenity-rs/serenity/commit/21eb42f96f9721d4e004dbc70aedf60e6d1ae7c4 +[c:2a6c3b1]: https://github.com/serenity-rs/serenity/commit/2a6c3b1d1e24ec7dc3b1f19baf87594e362ded27 +[c:4648f58]: https://github.com/serenity-rs/serenity/commit/4648f58e8ddc878d06a5a4a1d2840180c359ddf0 +[c:602c5a7]: https://github.com/serenity-rs/serenity/commit/602c5a7b78dda42b9c3d5426c39099d48e74bca5 +[c:73ab20f]: https://github.com/serenity-rs/serenity/commit/73ab20f271c9cc6dadb7bb76938ae64d19cee71e +[c:7a93557]: https://github.com/serenity-rs/serenity/commit/7a935574ffe0b7d19c1ed5c5befe1b7e7e4f0e0d +[c:8301333]: https://github.com/serenity-rs/serenity/commit/830133377a5832784c311302e543f86f85194e3b +[c:869fff5]: https://github.com/serenity-rs/serenity/commit/869fff566ca7a3669f7f08461a6bd481af3649d3 +[c:8918201]: https://github.com/serenity-rs/serenity/commit/891820102ff7b9025c67e03ac59f5ecd75959aac +[c:8c0e5a3]: https://github.com/serenity-rs/serenity/commit/8c0e5a377ad7db3c40e37740123c0ebf3d7e36ae +[c:8f128b2]: https://github.com/serenity-rs/serenity/commit/8f128b2c041d5f708378082af3653ff1ee2df919 +[c:90c7ec4]: https://github.com/serenity-rs/serenity/commit/90c7ec45d6cc01b25296de9619b7d3a6288244fe +[c:9568e3b]: https://github.com/serenity-rs/serenity/commit/9568e3b24816bb180740789d1e30c29f3658dc8b +[c:9a863bd]: https://github.com/serenity-rs/serenity/commit/9a863bd78e8edc5849e56e979888f1191b1d5845 +[c:a0b0dd2]: https://github.com/serenity-rs/serenity/commit/a0b0dd226f9ad2476729fa79dbc680bd08aa44b3 +[c:a4c3fec]: https://github.com/serenity-rs/serenity/commit/a4c3fec493d3b85ad1b43f3a5c4927d0d5cdc717 +[c:aa437d4]: https://github.com/serenity-rs/serenity/commit/aa437d4dbc4a59ffa65f80c7eafa6efc37eedc86 +[c:b324774]: https://github.com/serenity-rs/serenity/commit/b3247749f745c524b1eb0f44118c8358868e722a +[c:bbbf638]: https://github.com/serenity-rs/serenity/commit/bbbf63868a8ef3c0f21c1896f7afb96f4d8fbcc1 +[c:c458099]: https://github.com/serenity-rs/serenity/commit/c45809973f9ed333d9c13905a376af14a73d920b +[c:db21036]: https://github.com/serenity-rs/serenity/commit/db210367f3752d8e8ad018742ea0b590ddc54009 +[c:e1332a5]: https://github.com/serenity-rs/serenity/commit/e1332a54af46eff6051097ff4989c8d0fde4ca37 +[c:e2873c8]: https://github.com/serenity-rs/serenity/commit/e2873c820c1134ea7cc4cfbe99467aac350fa892 +[c:e290b03]: https://github.com/serenity-rs/serenity/commit/e290b038242cec6d4465f96c22cff24578f1a068 +[c:e5ea6c1]: https://github.com/serenity-rs/serenity/commit/e5ea6c176ba96988efc612a8e14eea90f9c293e1 +[c:f064d65]: https://github.com/serenity-rs/serenity/commit/f064d65486d0c8a3c510ee398e7d0bbf6b283bdb +[c:f3f22d7]: https://github.com/serenity-rs/serenity/commit/f3f22d7e072477028c9853d467dd18cf50e1589f + +[c:0067c33]: https://github.com/serenity-rs/serenity/commit/0067c3335929325f54a3a0fe3693703e16de219c +[c:04b0be1]: https://github.com/serenity-rs/serenity/commit/04b0be18b101186d618f9593fc8d2569ee845487 +[c:0d6e019]: https://github.com/serenity-rs/serenity/commit/0d6e019c258a8f2e743bcab196acab50b01e3958 +[c:10bbffe]: https://github.com/serenity-rs/serenity/commit/10bbffe9332edf8b8835d98cfffb8ec411162145 +[c:10f7548]: https://github.com/serenity-rs/serenity/commit/10f7548d4d57864b599dd7a760d2609144a2ec63 +[c:1ec1086]: https://github.com/serenity-rs/serenity/commit/1ec1086026971c903858128a8d38c5143f3f0f6f +[c:1f3a57e]: https://github.com/serenity-rs/serenity/commit/1f3a57eb6c0a1419614927d52bd3e798db36b043 +[c:29480e5]: https://github.com/serenity-rs/serenity/commit/29480e5eeccc12afc0e9020373647786736aabc7 +[c:2ef660e]: https://github.com/serenity-rs/serenity/commit/2ef660e34c4cca96ec30049e42c79e899c573be0 +[c:2ff765b]: https://github.com/serenity-rs/serenity/commit/2ff765bbe74e2dc36a6c0c221c7ab06aac74462a +[c:305d200]: https://github.com/serenity-rs/serenity/commit/305d2008216b5351d9fdd357381027ea42f4740b +[c:3121f90]: https://github.com/serenity-rs/serenity/commit/3121f90a9f98e82fab48d62cf95cd316ae9f0496 +[c:3a647e3]: https://github.com/serenity-rs/serenity/commit/3a647e3b7f6762fa6a078bc539e5b3e8012b37d4 +[c:40c8248]: https://github.com/serenity-rs/serenity/commit/40c8248d107b3c6cad785502e6d619669aba6431 +[c:4cf83d0]: https://github.com/serenity-rs/serenity/commit/4cf83d0d6b2a4fe156d3c54c06db4ce32293efb0 +[c:4e4dcb1]: https://github.com/serenity-rs/serenity/commit/4e4dcb11586520f798c831956dc42778c0205386 +[c:530ea76]: https://github.com/serenity-rs/serenity/commit/530ea76cfd05ffa64a826e6afa342860c730fd00 +[c:55555b8]: https://github.com/serenity-rs/serenity/commit/55555b88dd44366e27d2c7cc02166995a3835a69 +[c:5abc7d1]: https://github.com/serenity-rs/serenity/commit/5abc7d1d7fe7130e73e4848c6333627d9881cb9e +[c:5dab87b]: https://github.com/serenity-rs/serenity/commit/5dab87b0ff0097eb78abc1089c6a51ea05aa2273 +[c:5b66ace]: https://github.com/serenity-rs/serenity/commit/5b66ace77b55c3d7272aab9b49db919c180ec33f +[c:5ffdcea]: https://github.com/serenity-rs/serenity/commit/5ffdceafcbc75947365004107e640783ec033335 +[c:614402f]: https://github.com/serenity-rs/serenity/commit/614402f7b963a713bfa98bc5b1cfa968e8d6c103 +[c:6ddfef8]: https://github.com/serenity-rs/serenity/commit/6ddfef8359a619be9a49be7b33b466724eed0ecb +[c:703d135]: https://github.com/serenity-rs/serenity/commit/703d13564f9081839eb77e4e4699d711b1de895a +[c:7937025]: https://github.com/serenity-rs/serenity/commit/7937025a484955cc8d74fb10004ba8b49dcc2bb0 +[c:7b9764c]: https://github.com/serenity-rs/serenity/commit/7b9764cf1097b0620d871fabe67b5593f0cd4a4a +[c:7eac4d5]: https://github.com/serenity-rs/serenity/commit/7eac4d5fcf6c16db64e118de3d69825909979d5b +[c:8114a7a]: https://github.com/serenity-rs/serenity/commit/8114a7ace3ad51b9903a6017993aa526742bd72d +[c:8aefde0]: https://github.com/serenity-rs/serenity/commit/8aefde08465a050ad7bae12e6003fe514f43af5f +[c:8ce8234]: https://github.com/serenity-rs/serenity/commit/8ce82346846f235357b8dc53cb3ff399e70fcb4a +[c:93f453b]: https://github.com/serenity-rs/serenity/commit/93f453b07b9e8f813e6bfb0ddd2648a8e626d136 +[c:9b2cd75]: https://github.com/serenity-rs/serenity/commit/9b2cd75baf1fa7ee063f47e966ee3f6566a6d45c +[c:9da7669]: https://github.com/serenity-rs/serenity/commit/9da766976929417c4b8f487f8ec05b6f8b3f43ef +[c:9e45642]: https://github.com/serenity-rs/serenity/commit/9e456427ccd496c4128bde841df0c0af7a262047 +[c:9e56062]: https://github.com/serenity-rs/serenity/commit/9e560628deb1cf66e0c5029f41a79404fadffb40 +[c:a9a2c27]: https://github.com/serenity-rs/serenity/commit/a9a2c27d7aefa6061dd9ca58a96c5ba617a78a6a +[c:a9e8626]: https://github.com/serenity-rs/serenity/commit/a9e8626c4cd642087f828c5b32481bee9e4d368b +[c:aeb89af]: https://github.com/serenity-rs/serenity/commit/aeb89af4eff59bb3ea9eb7623685bf7ad7520496 +[c:b520ec7]: https://github.com/serenity-rs/serenity/commit/b520ec708c375e09838b9f25fd285790b856bb97 +[c:bbfc8e2]: https://github.com/serenity-rs/serenity/commit/bbfc8e2d0250f41d5bf4230b6efb428419133de8 +[c:bd4aa0a]: https://github.com/serenity-rs/serenity/commit/bd4aa0aabda4a2986e6145e3a793e8b2a391f8dd +[c:caeab28]: https://github.com/serenity-rs/serenity/commit/caeab28059d029a92b784f3b5ae1f79c412c8404 +[c:ccd2506]: https://github.com/serenity-rs/serenity/commit/ccd250649665b1726b0ca852b2375c113da6ed57 +[c:ce8da79]: https://github.com/serenity-rs/serenity/commit/ce8da793d3142cb001d9b155ff4224c15fe833ce +[c:d0d363f]: https://github.com/serenity-rs/serenity/commit/d0d363fb2a3475c68d40b02ec22ab728059fd55e +[c:d11d916]: https://github.com/serenity-rs/serenity/commit/d11d916a94b8a96fde218db4550d6c2428b4bc2a +[c:dd3744b]: https://github.com/serenity-rs/serenity/commit/dd3744b08887debba0d44fd0bceddef5f8ed1356 +[c:e602630]: https://github.com/serenity-rs/serenity/commit/e6026308b33c80aa33f0001c89cd271cc5cb6687 +[c:eae624e]: https://github.com/serenity-rs/serenity/commit/eae624e3f18681971a654c95624d917afe00695a +[c:f09b661]: https://github.com/serenity-rs/serenity/commit/f09b661be9085c7525a6c9f6929b50deebffae9b +[c:f0f06b7]: https://github.com/serenity-rs/serenity/commit/f0f06b7d3b890d2ddcb84e00b3f62e195da80090 + [c:0324e01]: https://github.com/serenity-rs/serenity/commit/0324e011f1ea0eed0709c92fe86319c812a42206 [c:08a7110]: https://github.com/serenity-rs/serenity/commit/08a71106748e356d2618e48d8797e6da60d7eb54 [c:0e1e8fb]: https://github.com/serenity-rs/serenity/commit/0e1e8fbbe564c23530a709a7ec407b08f63944e2 @@ -1,5 +1,5 @@ [package] -authors = ["alex <[email protected]>", "Zeyla Hellyer <[email protected]>"] +authors = ["alex <[email protected]>", "Zeyla Hellyer <[email protected]>"] description = "A Rust library for the Discord API." documentation = "https://docs.rs/serenity" homepage = "https://github.com/serenity-rs/serenity" @@ -8,7 +8,7 @@ license = "ISC" name = "serenity" readme = "README.md" repository = "https://github.com/serenity-rs/serenity.git" -version = "0.5.4" +version = "0.5.7" [dependencies] bitflags = "^1.0" @@ -1,6 +1,6 @@ ISC License (ISC) -Copyright (c) 2016, Zeyla Hellyer <[email protected]> +Copyright (c) 2016, Zeyla Hellyer <[email protected]> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice @@ -1,4 +1,4 @@ -[![ci-badge][]][ci] [![docs-badge][]][docs] [![guild-badge][]][guild] +[![ci-badge][]][ci] [![docs-badge][]][docs] [![guild-badge][]][guild] [![crates.io version]][crates.io link] [![rust 1.25+ badge]][rust 1.25+ link] # serenity @@ -89,7 +89,7 @@ and to the top of your `main.rs`: #[macro_use] extern crate serenity; ``` -Serenity only supports the _latest_ Stable, Beta, and Nightly. +Serenity supports a minimum of Rust 1.25. # Features @@ -103,6 +103,9 @@ features = ["pick", "your", "feature", "names", "here"] version = "0.5" ``` +The default features are: `builder`, `cache`, `client`, `framework`, `gateway`, +`http`, `model`, `standard_framework`, and `utils`. + The following is a full list of features: - **builder**: The builders used in conjunction with models' methods. @@ -124,6 +127,25 @@ the HTTP functions. - **voice**: Enables compilation of voice support, so that voice channels can be connected to and audio can be sent/received. +If you want all of the default features except for `cache` for example, you can +list all but that: + +```toml +[dependencies.serenity] +default-features = false +features = [ + "builder", + "client", + "framework", + "gateway", + "http", + "model", + "standard_framework", + "utils", +] +version = "0.5" +``` + # Dependencies Serenity requires the following dependencies: @@ -167,15 +189,17 @@ Voice+youtube-dl: [`validate_token`]: https://docs.rs/serenity/*/serenity/client/fn.validate_token.html [cache docs]: https://docs.rs/serenity/*/serenity/cache/index.html [ci]: https://travis-ci.org/serenity-rs/serenity -[ci-badge]: https://travis-ci.org/serenity-rs/serenity.svg?branch=master +[ci-badge]: https://img.shields.io/travis/serenity-rs/serenity.svg?style=flat-square [client's module-level documentation]: https://docs.rs/serenity/*/serenity/client/index.html +[crates.io link]: https://crates.io/crates/serenity +[crates.io version]: https://img.shields.io/crates/v/serenity.svg?style=flat-square [discord docs]: https://discordapp.com/developers/docs/intro [docs]: https://docs.rs/serenity -[docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg +[docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square [examples]: https://github.com/serenity-rs/serenity/tree/master/examples [gateway docs]: https://docs.rs/serenity/*/serenity/gateway/index.html [guild]: https://discord.gg/WBdGJCc -[guild-badge]: https://discordapp.com/api/guilds/381880193251409931/widget.png +[guild-badge]: https://img.shields.io/discord/381880193251409931.svg?style=flat-square&colorB=7289DA [library:Discord.net]: https://github.com/RogueException/Discord.Net [library:JDA]: https://github.com/DV8FromTheWorld/JDA [library:disco]: https://github.com/b1naryth1ef/disco @@ -183,3 +207,5 @@ Voice+youtube-dl: [library:discord.js]: https://github.com/hydrabolt/discord.js [library:discord.py]: https://github.com/Rapptz/discord.py [logo]: https://raw.githubusercontent.com/serenity-rs/serenity/master/logo.png +[rust 1.25+ badge]: https://img.shields.io/badge/rust-1.25+-93450a.svg?style=flat-square +[rust 1.25+ link]: https://blog.rust-lang.org/2018/03/29/Rust-1.25.html diff --git a/examples/05_command_framework/src/main.rs b/examples/05_command_framework/src/main.rs index be8ff41..a5494f0 100644 --- a/examples/05_command_framework/src/main.rs +++ b/examples/05_command_framework/src/main.rs @@ -168,10 +168,12 @@ fn main() { .bucket("complicated") .cmd(commands)) .group("Emoji", |g| g - // Sets a single prefix for a group: - .prefix("emoji") + // Sets multiple prefixes for a group. + // This requires us to call commands in this group + // via `~emoji` (or `~e`) instead of just `~`. + .prefixes(vec!["emoji", "em"]) // Sets a command that will be executed if only a group-prefix was passed. - .default_cmd(dog) + .default_cmd(bird) .command("cat", |c| c .desc("Sends an emoji with a cat.") .batch_known_as(vec!["kitty", "neko"]) // Adds multiple aliases @@ -184,10 +186,10 @@ fn main() { .bucket("emoji") .cmd(dog))) .group("Math", |g| g - // Sets multiple prefixes for a group. - // This requires us to call commands in this group - // via `~math` (or `~m`) instead of just `~`. - .prefixes(vec!["m", "math"]) + // Sets a single prefix for this group. + // So one has to call commands in this group + // via `~math` instead of just `~`. + .prefix("math") .command("multiply", |c| c .known_as("*") // Lets us also call `~math *` instead of just `~math multiply`. .cmd(multiply))) @@ -204,7 +206,7 @@ fn main() { .group("Owner", |g| g // This check applies to every command on this group. // User needs to pass the test for the command to execute. - .check(admin_check) + .check(admin_check) .command("am i admin", |c| c .cmd(am_i_admin)) .guild_only(true) @@ -249,7 +251,7 @@ fn owner_check(_: &mut Context, msg: &Message, _: &mut Args, _: &CommandOptions) // A function which acts as a "check", to determine whether to call a command. // -// This check analyses whether a guild member permissions has +// This check analyses whether a guild member permissions has // administrator-permissions. fn admin_check(_: &mut Context, msg: &Message, _: &mut Args, _: &CommandOptions) -> bool { if let Some(member) = msg.member() { @@ -381,3 +383,15 @@ command!(cat(_ctx, msg, _args) { println!("Error sending message: {:?}", why); } }); + +command!(bird(_ctx, msg, args) { + let say_content = if args.is_empty() { + ":bird: can find animals for you.".to_string() + } else { + format!(":bird: could not find animal named: `{}`.", args.full()) + }; + + if let Err(why) = msg.channel_id.say(say_content) { + println!("Error sending message: {:?}", why); + } +}); diff --git a/src/builder/create_embed.rs b/src/builder/create_embed.rs index 7604386..35172e1 100644 --- a/src/builder/create_embed.rs +++ b/src/builder/create_embed.rs @@ -515,3 +515,92 @@ impl<'a, Tz: TimeZone> From<&'a DateTime<Tz>> for Timestamp } } } + +#[cfg(test)] +mod test { + use model::channel::{Embed, EmbedField, EmbedFooter, EmbedImage, EmbedVideo}; + use serde_json::Value; + use super::CreateEmbed; + use utils::{self, Colour}; + + #[test] + fn test_from_embed() { + let embed = Embed { + author: None, + colour: Colour::new(0xFF0011), + description: Some("This is a test description".to_string()), + fields: vec![ + EmbedField { + inline: false, + name: "a".to_string(), + value: "b".to_string(), + }, + EmbedField { + inline: true, + name: "c".to_string(), + value: "z".to_string(), + }, + ], + footer: Some(EmbedFooter { + icon_url: Some("https://i.imgur.com/XfWpfCV.gif".to_string()), + proxy_icon_url: None, + text: "This is a hakase footer".to_string(), + }), + image: Some(EmbedImage { + height: 213, + proxy_url: "a".to_string(), + url: "https://i.imgur.com/XfWpfCV.gif".to_string(), + width: 224, + }), + kind: "rich".to_string(), + provider: None, + thumbnail: None, + timestamp: None, + title: Some("hakase".to_string()), + url: Some("https://i.imgur.com/XfWpfCV.gif".to_string()), + video: Some(EmbedVideo { + height: 213, + url: "https://i.imgur.com/XfWpfCV.mp4".to_string(), + width: 224, + }), + }; + + let builder = CreateEmbed::from(embed) + .colour(0xFF0011) + .description("This is a hakase description") + .image("https://i.imgur.com/XfWpfCV.gif") + .title("still a hakase") + .url("https://i.imgur.com/XfWpfCV.gif"); + + let built = Value::Object(utils::vecmap_to_json_map(builder.0)); + + let obj = json!({ + "color": 0xFF0011, + "description": "This is a hakase description", + "title": "still a hakase", + "type": "rich", + "url": "https://i.imgur.com/XfWpfCV.gif", + "fields": [ + { + "inline": false, + "name": "a", + "value": "b", + }, + { + "inline": true, + "name": "c", + "value": "z", + }, + ], + "image": { + "url": "https://i.imgur.com/XfWpfCV.gif", + }, + "footer": { + "text": "This is a hakase footer", + "icon_url": "https://i.imgur.com/XfWpfCV.gif", + } + }); + + assert_eq!(built, obj); + } +} diff --git a/src/builder/create_invite.rs b/src/builder/create_invite.rs index c9b00ff..c38924b 100644 --- a/src/builder/create_invite.rs +++ b/src/builder/create_invite.rs @@ -59,8 +59,8 @@ use utils::VecMap; /// client.start().unwrap(); /// ``` /// -/// [`GuildChannel::create_invite`]: ../model/guild/struct.GuildChannel.html#method.create_invite -/// [`RichInvite`]: ../model/guild/struct.Invite.html +/// [`GuildChannel::create_invite`]: ../model/channel/struct.GuildChannel.html#method.create_invite +/// [`RichInvite`]: ../model/invite/struct.RichInvite.html #[derive(Clone, Debug)] pub struct CreateInvite(pub VecMap<&'static str, Value>); diff --git a/src/builder/edit_guild.rs b/src/builder/edit_guild.rs index 28abda7..5db2051 100644 --- a/src/builder/edit_guild.rs +++ b/src/builder/edit_guild.rs @@ -10,7 +10,7 @@ use utils::VecMap; /// /// [`Guild::edit`]: ../model/guild/struct.Guild.html#method.edit /// [`Guild`]: ../model/guild/struct.Guild.html -/// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html +/// [Manage Guild]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[derive(Clone, Debug, Default)] pub struct EditGuild(pub VecMap<&'static str, Value>); diff --git a/src/builder/edit_member.rs b/src/builder/edit_member.rs index c0791d6..0fba010 100644 --- a/src/builder/edit_member.rs +++ b/src/builder/edit_member.rs @@ -15,7 +15,7 @@ impl EditMember { /// /// Requires the [Deafen Members] permission. /// - /// [Deafen Members]: ../model/permissions/constant.DEAFEN_MEMBERS.html + /// [Deafen Members]: ../model/permissions/struct.Permissions.html#associatedconstant.DEAFEN_MEMBERS pub fn deafen(mut self, deafen: bool) -> Self { self.0.insert("deaf", Value::Bool(deafen)); @@ -26,7 +26,7 @@ impl EditMember { /// /// Requires the [Mute Members] permission. /// - /// [Mute Members]: ../model/permissions/constant.MUTE_MEMBERS.html + /// [Mute Members]: ../model/permissions/struct.Permissions.html#associatedconstant.MUTE_MEMBERS pub fn mute(mut self, mute: bool) -> Self { self.0.insert("mute", Value::Bool(mute)); @@ -38,7 +38,7 @@ impl EditMember { /// /// Requires the [Manage Nicknames] permission. /// - /// [Manage Nicknames]: ../model/permissions/constant.MANAGE_NICKNAMES.html + /// [Manage Nicknames]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_NICKNAMES pub fn nickname(mut self, nickname: &str) -> Self { self.0.insert("nick", Value::String(nickname.to_string())); @@ -49,7 +49,7 @@ impl EditMember { /// /// Requires the [Manage Roles] permission to modify. /// - /// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES pub fn roles<T: AsRef<RoleId>, It: IntoIterator<Item=T>>(self, roles: It) -> Self { let roles = roles .into_iter() @@ -69,7 +69,7 @@ impl EditMember { /// /// Requires the [Move Members] permission. /// - /// [Move Members]: ../model/permissions/constant.MOVE_MEMBERS.html + /// [Move Members]: ../model/permissions/struct.Permissions.html#associatedconstant.MOVE_MEMBERS #[inline] pub fn voice_channel<C: Into<ChannelId>>(self, channel_id: C) -> Self { self._voice_channel(channel_id.into()) diff --git a/src/builder/edit_profile.rs b/src/builder/edit_profile.rs index df428b4..5bc0b92 100644 --- a/src/builder/edit_profile.rs +++ b/src/builder/edit_profile.rs @@ -45,7 +45,7 @@ impl EditProfile { /// # client.start().unwrap(); /// ``` /// - /// [`utils::read_image`]: ../fn.read_image.html + /// [`utils::read_image`]: ../utils/fn.read_image.html pub fn avatar(mut self, avatar: Option<&str>) -> Self { let avatar = avatar.map_or(Value::Null, |x| Value::String(x.to_string())); self.0.insert("avatar", avatar); diff --git a/src/cache/mod.rs b/src/cache/mod.rs index cf54d1a..0d4d069 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -13,7 +13,7 @@ //! //! # Use by Models //! -//! Most models of Discord objects, such as the [`Message`], [`PublicChannel`], +//! Most models of Discord objects, such as the [`Message`], [`GuildChannel`], //! or [`Emoji`], have methods for interacting with that single instance. This //! feature is only compiled if the `methods` feature is enabled. An example of //! this is [`Guild::edit`], which performs a check to ensure that the current @@ -37,7 +37,7 @@ //! [`Guild`]: ../model/guild/struct.Guild.html //! [`Guild::edit`]: ../model/guild/struct.Guild.html#method.edit //! [`Message`]: ../model/channel/struct.Message.html -//! [`PublicChannel`]: ../model/channel/struct.PublicChannel.html +//! [`GuildChannel`]: ../model/channel/struct.GuildChannel.html //! [`Role`]: ../model/guild/struct.Role.html //! [`CACHE`]: ../struct.CACHE.html //! [`http`]: ../http/index.html @@ -154,7 +154,6 @@ pub struct Cache { /// - [`GuildMemberAdd`][`GuildMemberAddEvent`] /// - [`GuildMemberRemove`][`GuildMemberRemoveEvent`] /// - [`GuildMembersChunk`][`GuildMembersChunkEvent`] - /// - [`GuildSync`][`GuildSyncEvent`] /// - [`PresenceUpdate`][`PresenceUpdateEvent`] /// - [`Ready`][`ReadyEvent`] /// @@ -167,7 +166,6 @@ pub struct Cache { /// [`GuildMemberRemoveEvent`]: ../model/event/struct.GuildMemberRemoveEvent.html /// [`GuildMemberUpdateEvent`]: ../model/event/struct.GuildMemberUpdateEvent.html /// [`GuildMembersChunkEvent`]: ../model/event/struct.GuildMembersChunkEvent.html - /// [`GuildSyncEvent`]: ../model/event/struct.GuildSyncEvent.html /// [`PresenceUpdateEvent`]: ../model/event/struct.PresenceUpdateEvent.html /// [`ReadyEvent`]: ../model/event/struct.ReadyEvent.html pub users: HashMap<UserId, Arc<RwLock<User>>>, @@ -391,7 +389,7 @@ impl Cache { /// The only advantage of this method is that you can pass in anything that /// is indirectly a [`GuildId`]. /// - /// [`GuildId`]: ../model/guild/struct.GuildId.html + /// [`GuildId`]: ../model/id/struct.GuildId.html /// /// # Examples /// @@ -711,7 +709,7 @@ impl Cache { /// The only advantage of this method is that you can pass in anything that /// is indirectly a [`UserId`]. /// - /// [`UserId`]: ../model/user/struct.UserId.html + /// [`UserId`]: ../model/id/struct.UserId.html /// [`users`]: #structfield.users /// /// # Examples @@ -802,3 +800,175 @@ impl Default for Cache { } } } + +#[cfg(test)] +mod test { + use chrono::DateTime; + use serde_json::{Number, Value}; + use std::{ + collections::HashMap, + sync::Arc, + }; + use { + cache::{Cache, CacheUpdate, Settings}, + model::prelude::*, + prelude::RwLock, + }; + + #[test] + fn test_cache_messages() { + let mut settings = Settings::new(); + settings.max_messages(2); + let mut cache = Cache::new_with_settings(settings); + + // Test inserting one message into a channel's message cache. + let datetime = DateTime::parse_from_str( + "1983 Apr 13 12:09:14.274 +0000", + "%Y %b %d %H:%M:%S%.3f %z", + ).unwrap(); + let mut event = MessageCreateEvent { + message: Message { + id: MessageId(3), + attachments: vec![], + author: User { + id: UserId(2), + avatar: None, + bot: false, + discriminator: 1, + name: "user 1".to_owned(), + }, + channel_id: ChannelId(2), + guild_id: Some(GuildId(1)), + content: String::new(), + edited_timestamp: None, + embeds: vec![], + kind: MessageType::Regular, + member: None, + mention_everyone: false, + mention_roles: vec![], + mentions: vec![], + nonce: Value::Number(Number::from(1)), + pinned: false, + reactions: vec![], + timestamp: datetime.clone(), + tts: false, + webhook_id: None, + }, + }; + // Check that the channel cache doesn't exist. + assert!(!cache.messages.contains_key(&event.message.channel_id)); + // Add first message, none because message ID 2 doesn't already exist. + assert!(event.update(&mut cache).is_none()); + // None, it only returns the oldest message if the cache was already full. + assert!(event.update(&mut cache).is_none()); + // Assert there's only 1 message in the channel's message cache. + assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 1); + + // Add a second message, assert that channel message cache length is 2. + event.message.id = MessageId(4); + assert!(event.update(&mut cache).is_none()); + assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 2); + + // Add a third message, the first should now be removed. + event.message.id = MessageId(5); + assert!(event.update(&mut cache).is_some()); + + { + let channel = cache.messages.get(&event.message.channel_id).unwrap(); + + assert_eq!(channel.len(), 2); + // Check that the first message is now removed. + assert!(!channel.contains_key(&MessageId(3))); + } + + let guild_channel = GuildChannel { + id: event.message.channel_id, + bitrate: None, + category_id: None, + guild_id: event.message.guild_id.unwrap(), + kind: ChannelType::Text, + last_message_id: None, + last_pin_timestamp: None, + name: String::new(), + permission_overwrites: vec![], + position: 0, + topic: None, + user_limit: None, + nsfw: false, + }; + + // Add a channel delete event to the cache, the cached messages for that + // channel should now be gone. + let mut delete = ChannelDeleteEvent { + channel: Channel::Guild(Arc::new(RwLock::new(guild_channel.clone()))), + }; + assert!(cache.update(&mut delete).is_none()); + assert!(!cache.messages.contains_key(&delete.channel.id())); + + // Test deletion of a guild channel's message cache when a GuildDeleteEvent + // is received. + let mut guild_create = { + let mut channels = HashMap::new(); + channels.insert(ChannelId(2), Arc::new(RwLock::new(guild_channel.clone()))); + + GuildCreateEvent { + guild: Guild { + id: GuildId(1), + afk_channel_id: None, + afk_timeout: 0, + application_id: None, + default_message_notifications: DefaultMessageNotificationLevel::All, + emojis: HashMap::new(), + explicit_content_filter: ExplicitContentFilter::None, + features: vec![], + icon: None, + joined_at: datetime, + large: false, + member_count: 0, + members: HashMap::new(), + mfa_level: MfaLevel::None, + name: String::new(), + owner_id: UserId(3), + presences: HashMap::new(), + region: String::new(), + roles: HashMap::new(), + splash: None, + system_channel_id: None, + verification_level: VerificationLevel::Low, + voice_states: HashMap::new(), + channels, + }, + } + }; + assert!(cache.update(&mut guild_create).is_none()); + assert!(cache.update(&mut event).is_none()); + + let mut guild_delete = GuildDeleteEvent { + guild: PartialGuild { + id: GuildId(1), + afk_channel_id: None, + afk_timeout: 0, + default_message_notifications: DefaultMessageNotificationLevel::All, + embed_channel_id: None, + embed_enabled: false, + emojis: HashMap::new(), + features: vec![], + icon: None, + mfa_level: MfaLevel::None, + name: String::new(), + owner_id: UserId(3), + region: String::new(), + roles: HashMap::new(), + splash: None, + verification_level: VerificationLevel::Low, + }, + }; + + // The guild existed in the cache, so the cache's guild is returned by the + // update. + assert!(cache.update(&mut guild_delete).is_some()); + + // Assert that the channel's message cache no longer exists. + assert!(!cache.messages.contains_key(&ChannelId(2))); + } +} diff --git a/src/client/bridge/gateway/mod.rs b/src/client/bridge/gateway/mod.rs index 27b5c10..a542dbc 100644 --- a/src/client/bridge/gateway/mod.rs +++ b/src/client/bridge/gateway/mod.rs @@ -1,4 +1,4 @@ -//! The client gateway bridge is support essential for the [`client`] module. +//! The client gateway bridge is support essential for the [`client`][client] module. //! //! This is made available for user use if one wishes to be lower-level or avoid //! the higher functionality of the [`Client`]. @@ -38,13 +38,14 @@ //! For almost every - if not every - use case, you only need to _possibly_ be //! concerned about the [`ShardManager`] in this module. //! +//! [client]: ../../index.html //! [`Client`]: ../../struct.Client.html -//! [`client`]: ../.. //! [`Shard`]: ../../../gateway/struct.Shard.html //! [`ShardManager`]: struct.ShardManager.html //! [`ShardManager::restart`]: struct.ShardManager.html#method.restart //! [`ShardManager::shutdown`]: struct.ShardManager.html#method.shutdown -//! [`ShardQueuer`]: struct.ShardQueuer.html +//! [`ShardManagerMessage`]: enum.ShardManagerMessage.html +//! [`ShardQueue`]: struct.ShardQueuer.html //! [`ShardRunner`]: struct.ShardRunner.html pub mod event; @@ -127,7 +128,7 @@ pub enum ShardManagerMessage { /// This should usually be wrapped in a [`ShardClientMessage`]. /// /// [`ShardClientMessage`]: enum.ShardClientMessage.html -/// [`ShardQueuer`]: enum.ShardQueuer.html +/// [`ShardQueuer`]: struct.ShardQueuer.html #[derive(Clone, Debug)] pub enum ShardQueuerMessage { /// Message to start a shard, where the 0-index element is the ID of the diff --git a/src/client/bridge/gateway/shard_manager_monitor.rs b/src/client/bridge/gateway/shard_manager_monitor.rs index e9a07cd..4989ef2 100644 --- a/src/client/bridge/gateway/shard_manager_monitor.rs +++ b/src/client/bridge/gateway/shard_manager_monitor.rs @@ -12,7 +12,7 @@ use super::{ShardManager, ShardManagerMessage}; /// receiving [`ShardManagerMessage`]s, such as whether to shutdown a shard or /// shutdown everything entirely. /// -/// [`ShardManagerMessage`]: struct.ShardManagerMessage.html +/// [`ShardManagerMessage`]: enum.ShardManagerMessage.html #[derive(Debug)] pub struct ShardManagerMonitor { /// An clone of the Arc to the manager itself. diff --git a/src/client/bridge/gateway/shard_messenger.rs b/src/client/bridge/gateway/shard_messenger.rs index 2331d4a..11e75ce 100644 --- a/src/client/bridge/gateway/shard_messenger.rs +++ b/src/client/bridge/gateway/shard_messenger.rs @@ -106,10 +106,9 @@ impl ShardMessenger { /// # } /// ``` /// - /// [`Event::GuildMembersChunk`]: - /// ../../model/event/enum.Event.html#variant.GuildMembersChunk - /// [`Guild`]: ../../model/guild/struct.Guild.html - /// [`Member`]: ../../model/guild/struct.Member.html + /// [`Event::GuildMembersChunk`]: ../../../model/event/enum.Event.html#variant.GuildMembersChunk + /// [`Guild`]: ../../../model/guild/struct.Guild.html + /// [`Member`]: ../../../model/guild/struct.Member.html pub fn chunk_guilds<It>( &self, guild_ids: It, @@ -147,9 +146,19 @@ impl ShardMessenger { /// # /// # let mut shard = Shard::new(mutex.clone(), mutex, [0, 1]).unwrap(); /// # + /// # #[cfg(feature = "model")] /// use serenity::model::gateway::Game; + /// # #[cfg(not(feature = "model"))] + /// use serenity::model::gateway::{Game, GameType}; /// + /// # #[cfg(feature = "model")] /// shard.set_game(Some(Game::playing("Heroes of the Storm"))); + /// # #[cfg(not(feature = "model"))] + /// shard.set_game(Some(Game { + /// kind: GameType::Playing, + /// name: "Heroes of the Storm".to_owned(), + /// url: None, + /// })); /// # Ok(()) /// # } /// # @@ -157,7 +166,11 @@ impl ShardMessenger { /// # try_main().unwrap(); /// # } /// ``` - pub fn set_game(&self, game: Option<Game>) { + pub fn set_game<T: Into<Game>>(&self, game: Option<T>) { + self._set_game(game.map(Into::into)) + } + + fn _set_game(&self, game: Option<Game>) { let _ = self.send(ShardRunnerMessage::SetGame(game)); } @@ -195,7 +208,15 @@ impl ShardMessenger { /// # try_main().unwrap(); /// # } /// ``` - pub fn set_presence(&self, game: Option<Game>, mut status: OnlineStatus) { + pub fn set_presence<T: Into<Game>>( + &self, + game: Option<T>, + status: OnlineStatus, + ) { + self._set_presence(game.map(Into::into), status) + } + + fn _set_presence(&self, game: Option<Game>, mut status: OnlineStatus) { if status == OnlineStatus::Offline { status = OnlineStatus::Invisible; } @@ -239,9 +260,9 @@ impl ShardMessenger { /// # } /// ``` /// - /// [`DoNotDisturb`]: ../../model/user/enum.OnlineStatus.html#variant.DoNotDisturb - /// [`Invisible`]: ../../model/user/enum.OnlineStatus.html#variant.Invisible - /// [`Offline`]: ../../model/user/enum.OnlineStatus.html#variant.Offline + /// [`DoNotDisturb`]: ../../../model/user/enum.OnlineStatus.html#variant.DoNotDisturb + /// [`Invisible`]: ../../../model/user/enum.OnlineStatus.html#variant.Invisible + /// [`Offline`]: ../../../model/user/enum.OnlineStatus.html#variant.Offline pub fn set_status(&self, mut online_status: OnlineStatus) { if online_status == OnlineStatus::Offline { online_status = OnlineStatus::Invisible; diff --git a/src/client/bridge/gateway/shard_runner_message.rs b/src/client/bridge/gateway/shard_runner_message.rs index 29024c9..f281d21 100644 --- a/src/client/bridge/gateway/shard_runner_message.rs +++ b/src/client/bridge/gateway/shard_runner_message.rs @@ -17,7 +17,7 @@ pub enum ShardRunnerMessage { /// The maximum number of members to receive [`GuildMembersChunkEvent`]s /// for. /// - /// [`GuildMembersChunkEvent`]: ../../../model/event/GuildMembersChunkEvent.html + /// [`GuildMembersChunkEvent`]: ../../../model/event/struct.GuildMembersChunkEvent.html limit: Option<u16>, /// Text to filter members by. /// diff --git a/src/client/bridge/mod.rs b/src/client/bridge/mod.rs index 41fcdec..a3a431d 100644 --- a/src/client/bridge/mod.rs +++ b/src/client/bridge/mod.rs @@ -1,4 +1,4 @@ -//! A collection of bridged support between the [`client`] module and other +//! A collection of bridged support between the [`client`](../index.html) module and other //! modules. //! //! **Warning**: You likely _do not_ need to mess with anything in here. Beware. diff --git a/src/client/context.rs b/src/client/context.rs index d581ffb..61a1925 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -87,6 +87,7 @@ impl Context { /// client.start().unwrap(); /// ``` #[cfg(feature = "builder")] + #[deprecated(since = "0.5.6", note = "Use the http module instead.")] pub fn edit_profile<F: FnOnce(EditProfile) -> EditProfile>(&self, f: F) -> Result<CurrentUser> { let mut map = VecMap::with_capacity(2); @@ -273,7 +274,7 @@ impl Context { /// [`set_presence`]: #method.set_presence #[inline] pub fn reset_presence(&self) { - self.shard.set_presence(None, OnlineStatus::Online); + self.shard.set_presence(None::<Game>, OnlineStatus::Online); } /// Sets the current game, defaulting to an online status of [`Online`]. @@ -316,7 +317,11 @@ impl Context { /// /// [`Online`]: ../model/user/enum.OnlineStatus.html#variant.Online #[inline] - pub fn set_game(&self, game: Game) { + pub fn set_game<T: Into<Game>>(&self, game: T) { + self._set_game(game.into()) + } + + fn _set_game(&self, game: Game) { self.shard.set_presence(Some(game), OnlineStatus::Online); } @@ -356,14 +361,10 @@ impl Context { /// [`Playing`]: ../model/gateway/enum.GameType.html#variant.Playing /// [`reset_presence`]: #method.reset_presence /// [`set_presence`]: #method.set_presence - pub fn set_game_name(&self, game_name: &str) { - let game = Game { - kind: GameType::Playing, - name: game_name.to_string(), - url: None, - }; - - self.shard.set_presence(Some(game), OnlineStatus::Online); + #[deprecated(since = "0.5.5", note = "Use Context::set_game")] + #[inline] + pub fn set_game_name<T: Into<String>>(&self, game_name: T) { + self.set_game(game_name.into()) } /// Sets the current user's presence, providing all fields to be passed. diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index dd5b4fe..7dc9024 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -15,14 +15,12 @@ use std::sync::mpsc::Sender; use threadpool::ThreadPool; use typemap::ShareMap; -#[cfg(feature = "cache")] -use chrono::{Timelike, Utc}; #[cfg(feature = "framework")] use framework::Framework; #[cfg(feature = "cache")] use model::id::GuildId; #[cfg(feature = "cache")] -use std::{thread, time}; +use std::time::Duration; #[cfg(feature = "cache")] use super::CACHE; @@ -32,17 +30,21 @@ macro_rules! update { { #[cfg(feature = "cache")] { - CACHE.write().update(&mut $event) + if let Some(mut lock) = CACHE.try_write_for(Duration::from_millis(10)) { + lock.update(&mut $event) + } else { + warn!( + "[dispatch] Possible deadlock: couldn't unlock cache to update with event: {:?}", + $event, + ); + + None + } } } }; } -#[cfg(feature = "cache")] -macro_rules! now { - () => (Utc::now().time().second() * 1000) -} - fn context( data: &Arc<Mutex<ShareMap>>, runner_tx: &Sender<InterMessage>, @@ -95,6 +97,7 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( } #[cfg(not(feature = "framework"))] +#[allow(unused_mut)] pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( event: DispatchEvent, data: &Arc<Mutex<ShareMap>>, @@ -104,7 +107,7 @@ pub(crate) fn dispatch<H: EventHandler + Send + Sync + 'static>( shard_id: u64, ) { match event { - DispatchEvent::Model(Event::MessageCreate(event)) => { + DispatchEvent::Model(Event::MessageCreate(mut event)) => { update!(event); let context = context(data, runner_tx, shard_id); @@ -149,20 +152,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( threadpool: &ThreadPool, shard_id: u64, ) { - #[cfg(feature = "cache")] - let mut last_guild_create_time = now!(); - - #[cfg(feature = "cache")] - let wait_for_guilds = move || -> ::Result<()> { - let unavailable_guilds = CACHE.read().unavailable_guilds.len(); - - while unavailable_guilds != 0 && (now!() < last_guild_create_time + 2000) { - thread::sleep(time::Duration::from_millis(500)); - } - - Ok(()) - }; - match event { DispatchEvent::Client(ClientEvent::ShardStageUpdate(event)) => { let context = context(data, runner_tx, shard_id); @@ -177,9 +166,9 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( let context = context(data, runner_tx, shard_id); - // This different channel_create dispatching is only due to the fact that - // each time the bot receives a dm, this event is also fired. - // So in short, only exists to reduce unnecessary clutter. + // Discord sends both a MessageCreate and a ChannelCreate upon a new message in a private channel. + // This could potentionally be annoying to handle when otherwise wanting to normally take care of a new channel. + // So therefore, private channels are dispatched to their own handler code. match event.channel { Channel::Private(channel) => { let event_handler = Arc::clone(event_handler); @@ -309,8 +298,6 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( #[cfg(feature = "cache")] { - last_guild_create_time = now!(); - let cache = CACHE.read(); if cache.unavailable_guilds.is_empty() { @@ -578,28 +565,12 @@ fn handle_event<H: EventHandler + Send + Sync + 'static>( DispatchEvent::Model(Event::Ready(mut event)) => { update!(event); - let event_handler = Arc::clone(event_handler); - - feature_cache! {{ - last_guild_create_time = now!(); - - let _ = wait_for_guilds() - .map(move |_| { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(&event_handler); - - threadpool.execute(move || { - event_handler.ready(context, event.ready); - }); - }); - } else { - let context = context(data, runner_tx, shard_id); - let event_handler = Arc::clone(&event_handler); + let context = context(data, runner_tx, shard_id); + let event_handler = Arc::clone(&event_handler); - threadpool.execute(move || { - event_handler.ready(context, event.ready); - }); - }} + threadpool.execute(move || { + event_handler.ready(context, event.ready); + }); }, DispatchEvent::Model(Event::Resumed(mut event)) => { let context = context(data, runner_tx, shard_id); diff --git a/src/client/mod.rs b/src/client/mod.rs index d5f8711..6fcb84c 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -92,10 +92,10 @@ use self::bridge::voice::ClientVoiceManager; /// client.start(); /// ``` /// -/// [`Shard`]: gateway/struct.Shard.html +/// [`Shard`]: ../gateway/struct.Shard.html /// [`EventHandler::message`]: trait.EventHandler.html#tymethod.message /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate -/// [sharding docs]: gateway/index.html#sharding +/// [sharding docs]: ../index.html#sharding pub struct Client { /// A ShareMap which requires types to be Send + Sync. This is a map that /// can be safely shared across contexts. @@ -169,13 +169,12 @@ pub struct Client { /// /// Refer to [example 05] for an example on using the `data` field. /// - /// [`Context::data`]: struct.Context.html#method.data + /// [`Context::data`]: struct.Context.html#structfield.data /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate /// [`Event::MessageDelete`]: ../model/event/enum.Event.html#variant.MessageDelete /// [`Event::MessageDeleteBulk`]: ../model/event/enum.Event.html#variant.MessageDeleteBulk /// [`Event::MessageUpdate`]: ../model/event/enum.Event.html#variant.MessageUpdate - /// [example 05]: - /// https://github.com/serenity-rs/serenity/tree/master/examples/05_command_framework + /// [example 05]: https://github.com/serenity-rs/serenity/tree/master/examples/05_command_framework pub data: Arc<Mutex<ShareMap>>, /// A vector of all active shards that have received their [`Event::Ready`] /// payload, and have dispatched to [`on_ready`] if an event handler was @@ -387,7 +386,7 @@ impl Client { } /// Sets a framework to be used with the client. All message events will be - /// passed through the framework _after_ being passed to the [`on_message`] + /// passed through the framework _after_ being passed to the [`message`] /// event handler. /// /// See the [framework module-level documentation][framework docs] for more @@ -486,7 +485,7 @@ impl Client { /// Refer to the documentation for the `framework` module for more in-depth /// information. /// - /// [`on_message`]: #method.on_message + /// [`message`]: trait.EventHandler.html#method.message /// [framework docs]: ../framework/index.html #[cfg(feature = "framework")] pub fn with_framework<F: Framework + Send + 'static>(&mut self, f: F) { @@ -534,7 +533,7 @@ impl Client { /// # } /// ``` /// - /// [gateway docs]: gateway/index.html#sharding + /// [gateway docs]: ../gateway/index.html#sharding pub fn start(&mut self) -> Result<()> { self.start_connection([0, 0, 1]) } @@ -586,7 +585,7 @@ impl Client { /// an error. /// /// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown - /// [gateway docs]: gateway/index.html#sharding + /// [gateway docs]: ../gateway/index.html#sharding pub fn start_autosharded(&mut self) -> Result<()> { let (x, y) = { let res = http::get_bot_gateway()?; @@ -673,7 +672,7 @@ impl Client { /// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown /// [`start`]: #method.start /// [`start_autosharded`]: #method.start_autosharded - /// [gateway docs]: gateway/index.html#sharding + /// [gateway docs]: ../gateway/index.html#sharding pub fn start_shard(&mut self, shard: u64, shards: u64) -> Result<()> { self.start_connection([shard, shard, shards]) } @@ -727,7 +726,7 @@ impl Client { /// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown /// [`start_shard`]: #method.start_shard /// [`start_shard_range`]: #method.start_shard_range - /// [Gateway docs]: gateway/index.html#sharding + /// [Gateway docs]: ../gateway/index.html#sharding pub fn start_shards(&mut self, total_shards: u64) -> Result<()> { self.start_connection([0, total_shards - 1, total_shards]) } @@ -797,7 +796,7 @@ impl Client { /// [`ClientError::Shutdown`]: enum.ClientError.html#variant.Shutdown /// [`start_shard`]: #method.start_shard /// [`start_shards`]: #method.start_shards - /// [Gateway docs]: gateway/index.html#sharding + /// [Gateway docs]: ../gateway/index.html#sharding pub fn start_shard_range(&mut self, range: [u64; 2], total_shards: u64) -> Result<()> { self.start_connection([range[0], range[1], total_shards]) } diff --git a/src/constants.rs b/src/constants.rs index 89a2085..5485d7e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -13,7 +13,7 @@ pub const LARGE_THRESHOLD: u8 = 250; pub const MESSAGE_CODE_LIMIT: u16 = 2000; /// The [UserAgent] sent along with every request. /// -/// [UserAgent]: ../hyper/header/struct.UserAgent.html +/// [UserAgent]: ../../hyper/header/struct.UserAgent.html pub const USER_AGENT: &str = concat!( "DiscordBot (https://github.com/serenity-rs/serenity, ", env!("CARGO_PKG_VERSION"), diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index bf3056b..2700bfb 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -7,10 +7,9 @@ use std::{ /// Defines how an operation on an `Args` method failed. #[derive(Debug)] pub enum Error<E: StdError> { - /// "END-OF-STRING", more precisely, there isn't anything to parse anymore. + /// "END-OF-STRING". There's nothing to parse anymore. Eos, - /// A parsing operation failed; the error in it can be of any returned from the `FromStr` - /// trait. + /// The parsing operation failed; the error can be anything returned from the `FromStr` trait. Parse(E), } @@ -200,34 +199,34 @@ impl<'a> Lexer<'a> { /// // You have a `photo` command that grabs the avatar url of a user. This command accepts names only. /// // Now, one of your users wants the avatar of a user named Princess Zelda. /// // Problem is, her name contains a space; our delimiter. This would result in two arguments, "Princess" and "Zelda". -/// // So how should we get around this? Through quotes! By surrounding her name in them we can perceive it as one single argument. +/// // So how shall we get around this? Through quotes! By surrounding her name in them we can perceive it as one single argument. /// let mut args = Args::new(r#""Princess Zelda""#, &[" ".to_string()]); /// /// // Hooray! /// assert_eq!(args.single_quoted::<String>().unwrap(), "Princess Zelda"); /// ``` /// -/// In case of a mistake, we can go back in time... er i mean, one step (or entirely): +/// In case of a mistake, we can go back in time... er I mean, one step (or entirely): /// /// ```rust /// use serenity::framework::standard::Args; /// -/// let mut args = Args::new("4 20", &[" ".to_string()]); +/// let mut args = Args::new("4 2", &[" ".to_string()]); /// /// assert_eq!(args.single::<u32>().unwrap(), 4); /// /// // Oh wait, oops, meant to double the 4. -/// // But i won't able to access it now... -/// // oh wait, i can `rewind`. +/// // But I won't able to access it now... +/// // oh wait, I can `rewind`. /// args.rewind(); /// /// assert_eq!(args.single::<u32>().unwrap() * 2, 8); /// -/// // And the same for the 20 -/// assert_eq!(args.single::<u32>().unwrap() * 2, 40); +/// // And the same for the 2 +/// assert_eq!(args.single::<u32>().unwrap() * 2, 4); /// -/// // WAIT, NO. I wanted to concatenate them into a "420" string... -/// // Argh, what should i do now???? +/// // WAIT, NO. I wanted to concatenate them into a "42" string... +/// // Argh, what should I do now???? /// // .... /// // oh, `restore` /// args.restore(); @@ -235,7 +234,7 @@ impl<'a> Lexer<'a> { /// let res = format!("{}{}", args.single::<String>().unwrap(), args.single::<String>().unwrap()); /// /// // Yay. -/// assert_eq!(res, "420"); +/// assert_eq!(res, "42"); /// ``` /// /// Hmm, taking a glance at the prior example, it seems we have an issue with reading the same argument over and over. @@ -244,19 +243,21 @@ impl<'a> Lexer<'a> { /// ```rust /// use serenity::framework::standard::Args; /// -/// let mut args = Args::new("four five six three", &[" ".to_string()]); +/// let mut args = Args::new("trois cinq quatre six", &[" ".to_string()]); /// -/// assert_eq!(args.single_n::<String>().unwrap(), "four"); +/// assert_eq!(args.single_n::<String>().unwrap(), "trois"); /// -/// // It might suggest we've lost the `four`, but in fact, we didn't! And not only that, we can do it an infinite amount of times! -/// assert_eq!(args.single_n::<String>().unwrap(), "four"); -/// assert_eq!(args.single_n::<String>().unwrap(), "four"); -/// assert_eq!(args.single_n::<String>().unwrap(), "four"); -/// assert_eq!(args.single_n::<String>().unwrap(), "four"); +/// // It might suggest we've lost the `trois`. But in fact, we didn't! And not only that, we can do it an infinite amount of times! +/// assert_eq!(args.single_n::<String>().unwrap(), "trois"); +/// assert_eq!(args.single_n::<String>().unwrap(), "trois"); +/// assert_eq!(args.single_n::<String>().unwrap(), "trois"); +/// assert_eq!(args.single_n::<String>().unwrap(), "trois"); /// -/// // Only if we use its parent method will we then lose it. -/// assert_eq!(args.single::<String>().unwrap(), "four"); -/// assert_eq!(args.single_n::<String>().unwrap(), "five"); +/// // Only if we use its brother method we'll then lose it. +/// assert_eq!(args.single::<String>().unwrap(), "trois"); +/// assert_eq!(args.single::<String>().unwrap(), "cinq"); +/// assert_eq!(args.single::<String>().unwrap(), "quatre"); +/// assert_eq!(args.single::<String>().unwrap(), "six"); /// ``` #[derive(Clone, Debug)] pub struct Args { @@ -276,11 +277,11 @@ impl Args { /// use serenity::framework::standard::Args; /// /// let mut args = Args::new( - /// // Our source from where we'll parse over. + /// // Our message from which we'll parse over. /// "the quick brown fox jumps over the lazy", /// /// // The "delimiters", or aka the separators. They denote how we distinguish arguments as their own. - /// // For this instance, we'll use one delimiter. The space (`0x20`), which will separate the arguments. + /// // For this example, we'll use one delimiter, the space (`0x20`), which will separate the message. /// &[" ".to_string()], /// ); /// @@ -288,7 +289,7 @@ impl Args { /// assert_eq!(args.single::<String>().unwrap(), "quick"); /// assert_eq!(args.single::<String>().unwrap(), "brown"); /// - /// // We should not see `the quick brown` again. + /// // We shall not see `the quick brown` again. /// assert_eq!(args.rest(), "fox jumps over the lazy"); /// ``` /// @@ -300,16 +301,21 @@ impl Args { .flat_map(|s| s.chars()) .collect::<Vec<_>>(); - let mut lex = Lexer::new(message, &delims); - let mut args = Vec::new(); - while let Some(token) = lex.commit() { - if token.kind == TokenKind::Delimiter { - continue; - } + // If there are no delimiters, then the only possible argument is the whole message. + if delims.is_empty() && !message.is_empty() { + args.push(Token::new(TokenKind::Argument, &message[..], 0)); + } else { + let mut lex = Lexer::new(message, &delims); + + while let Some(token) = lex.commit() { + if token.kind == TokenKind::Delimiter { + continue; + } - args.push(token); + args.push(token); + } } Args { @@ -319,6 +325,29 @@ impl Args { } } + /// Retrieves the current argument. Does not parse. + /// + /// # Note + /// + /// This borrows `Args` for the entire lifetime of the returned argument. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("4 2", &[" ".to_string()]); + /// + /// assert_eq!(args.current(), Some("4")); + /// args.next(); + /// assert_eq!(args.current(), Some("2")); + /// args.next(); + /// assert_eq!(args.current(), None); + /// ``` + pub fn current(&self) -> Option<&str> { + self.args.get(self.offset).map(|t| t.lit.as_str()) + } + /// Parses the current argument and advances. /// /// # Examples @@ -326,12 +355,12 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// let mut args = Args::new("4 2", &[" ".to_string()]); /// - /// assert_eq!(args.single::<u32>().unwrap(), 42); + /// assert_eq!(args.single::<u32>().unwrap(), 4); /// - /// // `42` is now out of the way, next we have `69` - /// assert_eq!(args.single::<u32>().unwrap(), 69); + /// // `4` is now out of the way. Next we have `2` + /// assert_eq!(args.single::<u32>().unwrap(), 2); /// ``` pub fn single<T: FromStr>(&mut self) -> Result<T, T::Err> where T::Err: StdError { @@ -353,10 +382,10 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let args = Args::new("42 69", &[" ".to_string()]); + /// let args = Args::new("4 2", &[" ".to_string()]); /// - /// assert_eq!(args.single_n::<u32>().unwrap(), 42); - /// assert_eq!(args.rest(), "42 69"); + /// assert_eq!(args.single_n::<u32>().unwrap(), 4); + /// assert_eq!(args.rest(), "4 2"); /// ``` /// /// [`single`]: #method.single @@ -371,17 +400,17 @@ impl Args { Ok(T::from_str(&cur.lit)?) } - /// "Skip" the argument (Sugar for `args.single::<String>().ok()`) + /// "Skip" the argument. Equivalent to `args.single::<String>().ok()`. /// /// # Examples /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// let mut args = Args::new("4 2", &[" ".to_string()]); /// /// args.skip(); - /// assert_eq!(args.single::<u32>().unwrap(), 69); + /// assert_eq!(args.single::<u32>().unwrap(), 2); /// ``` pub fn skip(&mut self) -> Option<String> { if self.is_empty() { @@ -398,11 +427,11 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69 88 99", &[" ".to_string()]); + /// let mut args = Args::new("man of culture topknot", &[" ".to_string()]); /// /// args.skip_for(3); /// assert_eq!(args.remaining(), 1); - /// assert_eq!(args.single::<u32>().unwrap(), 99); + /// assert_eq!(args.single::<String>().unwrap(), "topknot"); /// ``` /// /// [`skip`]: #method.skip @@ -420,17 +449,23 @@ impl Args { Some(vec) } - - /// Provides an iterator that will spew arguments until the end of the message. + /// Iterate until end of message. /// /// # Examples /// + /// Assert that all of the numbers in the message are even. + /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("3 4", &[" ".to_string()]); + /// let mut args = Args::new("4 2", &[" ".to_string()]); + /// + /// for arg in args.iter::<u32>() { + /// // Default to zero in case some linguist turns our numbers into words and can't parse those. + /// let arg = arg.unwrap_or(0); + /// assert!(arg % 2 == 0); + /// } /// - /// assert_eq!(*args.iter::<u32>().map(|num| num.unwrap().pow(2)).collect::<Vec<_>>(), [9, 16]); /// assert!(args.is_empty()); /// ``` pub fn iter<T: FromStr>(&mut self) -> Iter<T> @@ -438,16 +473,17 @@ impl Args { Iter::new(self) } - /// Parses all of the remaining arguments and returns them in a `Vec` (Sugar for `args.iter().collect::<Vec<_>>()`). + /// Parses all of the remaining arguments and returns them in a `Vec`. + /// Equivalent to `args.iter().collect::<Vec<_>>()`. /// /// # Examples /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let args = Args::new("42 69", &[" ".to_string()]); + /// let args = Args::new("4 2", &[" ".to_string()]); /// - /// assert_eq!(*args.multiple::<u32>().unwrap(), [42, 69]); + /// assert_eq!(*args.multiple::<u32>().unwrap(), [4, 2]); /// ``` pub fn multiple<T: FromStr>(mut self) -> Result<Vec<T>, T::Err> where T::Err: StdError { @@ -458,6 +494,30 @@ impl Args { self.iter::<T>().collect() } + /// Retrieves the current argument and also removes quotes around it if they're present. + /// Does not parse. + /// + /// # Note + /// + /// This borrows `Args` for the entire lifetime of the returned argument. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("4 \"2\"", &[" ".to_string()]); + /// + /// assert_eq!(args.current_quoted(), Some("4")); + /// args.next(); + /// assert_eq!(args.current_quoted(), Some("2")); + /// args.next(); + /// assert_eq!(args.current_quoted(), None); + /// ``` + pub fn current_quoted(&self) -> Option<&str> { + self.args.get(self.offset).map(|t| quotes_extract(t)) + } + /// Like [`single`], but accounts quotes. /// /// # Examples @@ -465,9 +525,9 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new(r#""42 69""#, &[" ".to_string()]); + /// let mut args = Args::new(r#""4 2""#, &[" ".to_string()]); /// - /// assert_eq!(args.single_quoted::<String>().unwrap(), "42 69"); + /// assert_eq!(args.single_quoted::<String>().unwrap(), "4 2"); /// assert!(args.is_empty()); /// ``` /// @@ -495,10 +555,10 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new(r#""42 69""#, &[" ".to_string()]); + /// let mut args = Args::new(r#""4 2""#, &[" ".to_string()]); /// - /// assert_eq!(args.single_quoted_n::<String>().unwrap(), "42 69"); - /// assert_eq!(args.rest(), r#""42 69""#); + /// assert_eq!(args.single_quoted_n::<String>().unwrap(), "4 2"); + /// assert_eq!(args.rest(), r#""4 2""#); /// ``` /// /// [`single_quoted`]: #method.single_quoted @@ -519,12 +579,19 @@ impl Args { /// /// # Examples /// + /// Assert that all of the numbers in quotations in the message are odd. + /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new(r#""2" "5""#, &[" ".to_string()]); + /// let mut args = Args::new(r#""5" "3""#, &[" ".to_string()]); + /// + /// for arg in args.iter_quoted::<u32>() { + /// // Default to zero in case some linguist turns our numbers into words and can't parse those. + /// let arg = arg.unwrap_or(0); + /// assert!(arg % 2 != 0); + /// } /// - /// assert_eq!(*args.iter_quoted::<u32>().map(|n| n.unwrap().pow(2)).collect::<Vec<_>>(), [4, 25]); /// assert!(args.is_empty()); /// ``` /// @@ -541,9 +608,9 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new(r#""42" "69""#, &[" ".to_string()]); + /// let mut args = Args::new(r#""4" "2""#, &[" ".to_string()]); /// - /// assert_eq!(*args.multiple_quoted::<u32>().unwrap(), [42, 69]); + /// assert_eq!(*args.multiple_quoted::<u32>().unwrap(), [4, 2]); /// ``` /// /// [`multiple`]: #method.multiple @@ -559,20 +626,20 @@ impl Args { /// Returns the first argument that can be parsed and removes it from the message. The suitable argument /// can be in an arbitrary position in the message. Likewise, takes quotes into account. /// - /// **Note**: - /// Unlike how other methods on this struct work, - /// this function permantently removes the argument if it was **found** and was **succesfully** parsed. - /// Hence, use this with caution. + /// # Note + /// + /// Unlike the rest, this function permantently removes the argument if it was **found** and was **succesfully** parsed. + /// Hence, use with caution. /// /// # Examples /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("c42 69", &[" ".to_string()]); + /// let mut args = Args::new("c4 2", &[" ".to_string()]); /// - /// assert_eq!(args.find::<u32>().unwrap(), 69); - /// assert_eq!(args.single::<String>().unwrap(), "c42"); + /// assert_eq!(args.find::<u32>().unwrap(), 2); + /// assert_eq!(args.single::<String>().unwrap(), "c4"); /// assert!(args.is_empty()); /// ``` pub fn find<T: FromStr>(&mut self) -> Result<T, T::Err> @@ -600,13 +667,13 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("c42 69", &[" ".to_string()]); + /// let mut args = Args::new("c4 2", &[" ".to_string()]); /// - /// assert_eq!(args.find_n::<u32>().unwrap(), 69); + /// assert_eq!(args.find_n::<u32>().unwrap(), 2); /// /// // The `69` is still here, so let's parse it again. - /// assert_eq!(args.single::<String>().unwrap(), "c42"); - /// assert_eq!(args.single::<u32>().unwrap(), 69); + /// assert_eq!(args.single::<String>().unwrap(), "c4"); + /// assert_eq!(args.single::<u32>().unwrap(), 2); /// assert!(args.is_empty()); /// ``` /// @@ -675,12 +742,7 @@ impl Args { return s; } - let end = s.rfind('"'); - if end.is_none() { - return s; - } - - let end = end.unwrap(); + let end = s.rfind('"').unwrap(); // If it got the quote at the start, then there's no closing quote. if end == 0 { @@ -697,17 +759,17 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69 91", &[" ".to_string()]); + /// let mut args = Args::new("to tre fire", &[" ".to_string()]); /// - /// assert_eq!(args.rest(), "42 69 91"); + /// assert_eq!(args.rest(), "to tre fire"); /// /// args.skip(); /// - /// assert_eq!(args.rest(), "69 91"); + /// assert_eq!(args.rest(), "tre fire"); /// /// args.skip(); /// - /// assert_eq!(args.rest(), "91"); + /// assert_eq!(args.rest(), "fire"); /// /// args.skip(); /// @@ -729,7 +791,8 @@ impl Args { /// The full amount of recognised arguments. /// - /// **Note**: + /// # Note + /// /// This never changes. Except for [`find`], which upon success, subtracts the length by 1. (e.g len of `3` becomes `2`) /// /// # Examples @@ -737,9 +800,9 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// let mut args = Args::new("4 2", &[" ".to_string()]); /// - /// assert_eq!(args.len(), 2); // `2` because `["42", "69"]` + /// assert_eq!(args.len(), 2); // `2` because `["4", "2"]` /// ``` /// /// [`find`]: #method.find @@ -756,7 +819,8 @@ impl Args { /// /// let mut args = Args::new("", &[" ".to_string()]); /// - /// assert!(args.is_empty()); // `true` because passed message is empty thus no arguments. + /// // will be `true` because passed message is empty thus no arguments. + /// assert!(args.is_empty()); /// ``` pub fn is_empty(&self) -> bool { self.offset >= self.args.len() @@ -769,7 +833,7 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// let mut args = Args::new("2 4", &[" ".to_string()]); /// /// assert_eq!(args.remaining(), 2); /// @@ -785,22 +849,43 @@ impl Args { self.len() - self.offset } + /// Move to the next argument. + /// This increments the offset pointer. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new("4 2", &[" ".to_string()]); + /// + /// args.next(); + /// + /// assert_eq!(args.single::<u32>().unwrap(), 2); + /// assert!(args.is_empty()); + /// ``` + #[inline] + pub fn next(&mut self) { + self.offset += 1; + } + /// Go one step behind. + /// This decrements the offset pointer. /// /// # Examples /// /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69", &[" ".to_string()]); + /// let mut args = Args::new("4 2", &[" ".to_string()]); /// - /// assert_eq!(args.single::<u32>().unwrap(), 42); + /// assert_eq!(args.single::<u32>().unwrap(), 4); /// - /// // By this point, we can only parse 69 now. - /// // However, with the help of `rewind`, we can mess with 42 again. + /// // By this point, we can only parse 2 now. + /// // However, with the help of `rewind`, we can mess with 4 again. /// args.rewind(); /// - /// assert_eq!(args.single::<u32>().unwrap() * 2, 84); + /// assert_eq!(args.single::<u32>().unwrap() * 2, 8); /// ``` #[inline] pub fn rewind(&mut self) { @@ -818,12 +903,12 @@ impl Args { /// ```rust /// use serenity::framework::standard::Args; /// - /// let mut args = Args::new("42 69 95", &[" ".to_string()]); + /// let mut args = Args::new("42 420 69", &[" ".to_string()]); /// /// // Let's parse 'em numbers! /// assert_eq!(args.single::<u32>().unwrap(), 42); + /// assert_eq!(args.single::<u32>().unwrap(), 420); /// assert_eq!(args.single::<u32>().unwrap(), 69); - /// assert_eq!(args.single::<u32>().unwrap(), 95); /// /// // Oh, no! I actually wanted to multiply all of them by 2! /// // I don't want to call `rewind` 3 times manually.... @@ -831,8 +916,8 @@ impl Args { /// args.restore(); /// /// assert_eq!(args.single::<u32>().unwrap() * 2, 84); + /// assert_eq!(args.single::<u32>().unwrap() * 2, 840); /// assert_eq!(args.single::<u32>().unwrap() * 2, 138); - /// assert_eq!(args.single::<u32>().unwrap() * 2, 190); /// ``` /// #[inline] @@ -944,3 +1029,445 @@ fn quotes_extract(token: &Token) -> &str { &token.lit } } + +#[cfg(test)] +mod test { + use super::{Args, Error as ArgError}; + + #[test] + fn single_with_empty_message() { + let mut args = Args::new("", &["".to_string()]); + assert_matches!(args.single::<String>().unwrap_err(), ArgError::Eos); + + let mut args = Args::new("", &[",".to_string()]); + assert_matches!(args.single::<String>().unwrap_err(), ArgError::Eos); + } + + #[test] + fn single_n_with_empty_message() { + let args = Args::new("", &["".to_string()]); + assert_matches!(args.single_n::<String>().unwrap_err(), ArgError::Eos); + + let args = Args::new("", &[",".to_string()]); + assert_matches!(args.single_n::<String>().unwrap_err(), ArgError::Eos); + } + + #[test] + fn single_quoted_with_empty_message() { + let mut args = Args::new("", &["".to_string()]); + assert_matches!(args.single_quoted::<String>().unwrap_err(), ArgError::Eos); + + let mut args = Args::new("", &[",".to_string()]); + assert_matches!(args.single_quoted::<String>().unwrap_err(), ArgError::Eos); + } + + #[test] + fn multiple_with_empty_message() { + let args = Args::new("", &["".to_string()]); + assert_matches!(args.multiple::<String>().unwrap_err(), ArgError::Eos); + + let args = Args::new("", &[",".to_string()]); + assert_matches!(args.multiple::<String>().unwrap_err(), ArgError::Eos); + } + + #[test] + fn multiple_quoted_with_empty_message() { + let args = Args::new("", &["".to_string()]); + assert_matches!(args.multiple_quoted::<String>().unwrap_err(), ArgError::Eos); + + let args = Args::new("", &[",".to_string()]); + assert_matches!(args.multiple_quoted::<String>().unwrap_err(), ArgError::Eos); + } + + #[test] + fn skip_with_empty_message() { + let mut args = Args::new("", &["".to_string()]); + assert_matches!(args.skip(), None); + + let mut args = Args::new("", &[",".to_string()]); + assert_matches!(args.skip(), None); + } + + #[test] + fn skip_for_with_empty_message() { + let mut args = Args::new("", &["".to_string()]); + assert_matches!(args.skip_for(0), None); + + let mut args = Args::new("", &["".to_string()]); + assert_matches!(args.skip_for(5), None); + + let mut args = Args::new("", &[",".to_string()]); + assert_matches!(args.skip_for(0), None); + + let mut args = Args::new("", &[",".to_string()]); + assert_matches!(args.skip_for(5), None); + } + + #[test] + fn single_i32_with_2_bytes_long_delimiter() { + let mut args = Args::new("1, 2", &[", ".to_string()]); + + assert_eq!(args.single::<i32>().unwrap(), 1); + assert_eq!(args.single::<i32>().unwrap(), 2); + } + + #[test] + fn single_i32_with_1_byte_long_delimiter_i32() { + let mut args = Args::new("1,2", &[",".to_string()]); + + assert_eq!(args.single::<i32>().unwrap(), 1); + assert_eq!(args.single::<i32>().unwrap(), 2); + } + + #[test] + fn single_i32_with_wrong_char_after_first_arg() { + let mut args = Args::new("1, 2", &[",".to_string()]); + + assert_eq!(args.single::<i32>().unwrap(), 1); + assert!(args.single::<i32>().is_err()); + } + + #[test] + fn single_i32_with_one_character_being_3_bytes_long() { + let mut args = Args::new("1★2", &["★".to_string()]); + + assert_eq!(args.single::<i32>().unwrap(), 1); + assert_eq!(args.single::<i32>().unwrap(), 2); + } + + #[test] + fn single_i32_with_untrimmed_whitespaces() { + let mut args = Args::new(" 1, 2 ", &[",".to_string()]); + + assert!(args.single::<i32>().is_err()); + } + + #[test] + fn single_i32_n() { + let args = Args::new("1,2", &[",".to_string()]); + + assert_eq!(args.single_n::<i32>().unwrap(), 1); + assert_eq!(args.single_n::<i32>().unwrap(), 1); + } + + #[test] + fn single_quoted_chaining() { + let mut args = Args::new(r#""1, 2" "2" """#, &[" ".to_string()]); + + assert_eq!(args.single_quoted::<String>().unwrap(), "1, 2"); + assert_eq!(args.single_quoted::<String>().unwrap(), "2"); + assert_eq!(args.single_quoted::<String>().unwrap(), ""); + } + + #[test] + fn single_quoted_and_single_chaining() { + let mut args = Args::new(r#""1, 2" "2" "3" 4"#, &[" ".to_string()]); + + assert_eq!(args.single_quoted::<String>().unwrap(), "1, 2"); + assert!(args.single_n::<i32>().is_err()); + assert_eq!(args.single::<String>().unwrap(), "\"2\""); + assert_eq!(args.single_quoted::<i32>().unwrap(), 3); + assert_eq!(args.single::<i32>().unwrap(), 4); + } + + #[test] + fn full_on_args() { + let test_text = "Some text to ensure `full()` works."; + let args = Args::new(test_text, &[" ".to_string()]); + + assert_eq!(args.full(), test_text); + } + + #[test] + fn multiple_quoted_strings_one_delimiter() { + let args = Args::new(r#""1, 2" "a" "3" 4 "5"#, &[" ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", "4", "\"5"]); + } + + #[test] + fn multiple_quoted_strings_with_multiple_delimiter() { + let args = Args::new(r#""1, 2" "a","3"4 "5"#, &[" ".to_string(), ",".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", "4", "\"5"]); + } + + #[test] + fn multiple_quoted_strings_with_multiple_delimiters() { + let args = Args::new(r#""1, 2" "a","3" """#, &[" ".to_string(), ",".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", ""]); + } + + #[test] + fn multiple_quoted_i32() { + let args = Args::new(r#""1" "2" 3"#, &[" ".to_string()]); + + assert_eq!(args.multiple_quoted::<i32>().unwrap(), [1, 2, 3]); + } + + #[test] + fn multiple_quoted_quote_appears_without_delimiter_in_front() { + let args = Args::new(r#"hello, my name is cake" 2"#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "my", "name", "is", "cake\"", "2"]); + } + + #[test] + fn multiple_quoted_single_quote() { + let args = Args::new(r#"hello "2 b"#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "\"2 b"]); + } + + #[test] + fn multiple_quoted_one_quote_pair() { + let args = Args::new(r#"hello "2 b""#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "2 b"]); + } + + + #[test] + fn delimiter_before_multiple_quoted() { + let args = Args::new(r#","hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); + } + + #[test] + fn no_quote() { + let args = Args::new("hello, my name is cake", &[",".to_string(), " ".to_string()]); + + assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello"); + } + + #[test] + fn single_quoted_n() { + let args = Args::new(r#""hello, my name is cake","test"#, &[",".to_string()]); + + assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello, my name is cake"); + assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello, my name is cake"); + } + + #[test] + fn multiple_quoted_starting_with_wrong_delimiter_in_first_quote() { + let args = Args::new(r#""hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); + } + + #[test] + fn multiple_quoted_with_one_correct_and_one_invalid_quote() { + let args = Args::new(r#""hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); + + assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); + } + + #[test] + fn find_i32_one_one_byte_delimiter() { + let mut args = Args::new("hello,my name is cake 2", &[" ".to_string()]); + + assert_eq!(args.find::<i32>().unwrap(), 2); + } + + #[test] + fn find_i32_one_three_byte_delimiter() { + let mut args = Args::new("hello,my name is cakeé2", &["é".to_string()]); + + assert_eq!(args.find::<i32>().unwrap(), 2); + } + + #[test] + fn find_i32_multiple_delimiter_but_i32_not_last() { + let mut args = Args::new("hello,my name is 2 cake", &[" ".to_string(), ",".to_string()]); + + assert_eq!(args.find::<i32>().unwrap(), 2); + } + + #[test] + fn find_i32_multiple_delimiter() { + let mut args = Args::new("hello,my name is cake 2", &[" ".to_string(), ",".to_string()]); + + assert_eq!(args.find::<i32>().unwrap(), 2); + } + + #[test] + fn find_n_i32() { + let mut args = Args::new("a 2", &[" ".to_string()]); + + assert_eq!(args.find_n::<i32>().unwrap(), 2); + assert_eq!(args.find_n::<i32>().unwrap(), 2); + } + + #[test] + fn skip() { + let mut args = Args::new("1 2", &[" ".to_string()]); + + assert_eq!(args.skip().unwrap(), "1"); + assert_eq!(args.remaining(), 1); + assert_eq!(args.single::<String>().unwrap(), "2"); + } + + #[test] + fn skip_for() { + let mut args = Args::new("1 2 neko 100", &[" ".to_string()]); + + assert_eq!(args.skip_for(2).unwrap(), ["1", "2"]); + assert_eq!(args.remaining(), 2); + assert_eq!(args.single::<String>().unwrap(), "neko"); + assert_eq!(args.single::<String>().unwrap(), "100"); + } + + #[test] + fn len_with_one_delimiter() { + let args = Args::new("1 2 neko 100", &[" ".to_string()]); + + assert_eq!(args.len(), 4); + assert_eq!(args.remaining(), 4); + } + + #[test] + fn len_multiple_quoted() { + let args = Args::new(r#""hello, my name is cake" "2""#, &[" ".to_string()]); + + assert_eq!(args.len(), 2); + } + + #[test] + fn remaining_len_before_and_after_single() { + let mut args = Args::new("1 2", &[" ".to_string()]); + + assert_eq!(args.remaining(), 2); + assert_eq!(args.single::<i32>().unwrap(), 1); + assert_eq!(args.remaining(), 1); + assert_eq!(args.single::<i32>().unwrap(), 2); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_single_quoted() { + let mut args = Args::new(r#""1" "2" "3""#, &[" ".to_string()]); + + assert_eq!(args.remaining(), 3); + assert_eq!(args.single_quoted::<i32>().unwrap(), 1); + assert_eq!(args.remaining(), 2); + assert_eq!(args.single_quoted::<i32>().unwrap(), 2); + assert_eq!(args.remaining(), 1); + assert_eq!(args.single_quoted::<i32>().unwrap(), 3); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_skip() { + let mut args = Args::new("1 2", &[" ".to_string()]); + + assert_eq!(args.remaining(), 2); + assert_eq!(args.skip().unwrap(), "1"); + assert_eq!(args.remaining(), 1); + assert_eq!(args.skip().unwrap(), "2"); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_skip_empty_string() { + let mut args = Args::new("", &[" ".to_string()]); + + assert_eq!(args.remaining(), 0); + assert_eq!(args.skip(), None); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_skip_for() { + let mut args = Args::new("1 2", &[" ".to_string()]); + + assert_eq!(args.remaining(), 2); + assert_eq!(args.skip_for(2), Some(vec!["1".to_string(), "2".to_string()])); + assert_eq!(args.skip_for(2), None); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_find() { + let mut args = Args::new("a 2 6", &[" ".to_string()]); + + assert_eq!(args.remaining(), 3); + assert_eq!(args.find::<i32>().unwrap(), 2); + assert_eq!(args.remaining(), 2); + assert_eq!(args.find::<i32>().unwrap(), 6); + assert_eq!(args.remaining(), 1); + assert_eq!(args.find::<String>().unwrap(), "a"); + assert_eq!(args.remaining(), 0); + assert_matches!(args.find::<String>().unwrap_err(), ArgError::Eos); + assert_eq!(args.remaining(), 0); + } + + #[test] + fn remaining_len_before_and_after_find_n() { + let mut args = Args::new("a 2 6", &[" ".to_string()]); + + assert_eq!(args.remaining(), 3); + assert_eq!(args.find_n::<i32>().unwrap(), 2); + assert_eq!(args.remaining(), 3); + } + + + #[test] + fn multiple_strings_with_one_delimiter() { + let args = Args::new("hello, my name is cake 2", &[" ".to_string()]); + + assert_eq!(args.multiple::<String>().unwrap(), ["hello,", "my", "name", "is", "cake", "2"]); + } + + #[test] + fn multiple_i32_with_one_delimiter() { + let args = Args::new("1 2 3", &[" ".to_string()]); + + assert_eq!(args.multiple::<i32>().unwrap(), [1, 2, 3]); + } + + #[test] + fn multiple_i32_with_one_delimiter_and_parse_error() { + let args = Args::new("1 2 3 abc", &[" ".to_string()]); + + assert_matches!(args.multiple::<i32>().unwrap_err(), ArgError::Parse(_)); + } + + #[test] + fn multiple_i32_with_three_delimiters() { + let args = Args::new("1 2 3", &[" ".to_string(), ",".to_string()]); + + assert_eq!(args.multiple::<i32>().unwrap(), [1, 2, 3]); + } + + #[test] + fn single_after_failed_single() { + let mut args = Args::new("b 2", &[" ".to_string()]); + + assert_matches!(args.single::<i32>().unwrap_err(), ArgError::Parse(_)); + // Test that `single` short-circuts on an error and leaves the source as is. + assert_eq!(args.remaining(), 2); + assert_eq!(args.single::<String>().unwrap(), "b"); + assert_eq!(args.single::<String>().unwrap(), "2"); + } + + #[test] + fn remaining_len_after_failed_single_quoted() { + let mut args = Args::new("b a", &[" ".to_string()]); + + assert_eq!(args.remaining(), 2); + // Same goes for `single_quoted` and the alike. + assert_matches!(args.single_quoted::<i32>().unwrap_err(), ArgError::Parse(_)); + assert_eq!(args.remaining(), 2); + } + + #[test] + fn no_delims_entire_message() { + let mut args = Args::new("abc", &[]); + + assert_eq!(args.remaining(), 1); + assert_eq!(args.single::<String>().unwrap(), "abc"); + assert_eq!(args.remaining(), 0); + } +} diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index a6a6074..42264f2 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -338,15 +338,14 @@ impl Default for CommandOptions { } pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> { - if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() { - // Find out if they were mentioned. If not, determine if the prefix - // was used. If not, return None. - let mut positions: Vec<usize> = vec![]; + // Mentions have the highest precedence. + if let Some(mention_end) = find_mention_end(&msg.content, conf) { + return Some(vec![mention_end]); // This can simply be returned without trying to find the end whitespaces as trim will remove it later + } - if let Some(mention_end) = find_mention_end(&msg.content, conf) { - positions.push(mention_end); - return Some(positions); - } + if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() { + // Determine if a prefix was used. Otherwise return None. + let mut positions = Vec::new(); // Dynamic prefixes, if present and suitable, always have a higher priority. if let Some(x) = conf.dynamic_prefix.as_ref().and_then(|f| f(ctx, msg)) { @@ -390,10 +389,6 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti } Some(positions) - } else if conf.on_mention.is_some() { - find_mention_end(&msg.content, conf).map(|mention_end| { - vec![mention_end] // This can simply be returned without trying to find the end whitespaces as trim will remove it later - }) } else { None } diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs index f3d66d6..795493d 100644 --- a/src/framework/standard/configuration.rs +++ b/src/framework/standard/configuration.rs @@ -10,12 +10,14 @@ use std::{ }; use super::command::PrefixCheck; -/// The configuration to use for a [`Framework`] associated with a [`Client`] +/// The configuration to use for a [`StandardFramework`] associated with a [`Client`] /// instance. /// /// This allows setting configurations like the depth to search for commands, /// whether to treat mentions like a command prefix, etc. /// +/// To see the default values, refer to the [default implementation]. +/// /// # Examples /// /// Responding to mentions and setting a command prefix of `"~"`: @@ -38,7 +40,8 @@ use super::command::PrefixCheck; /// ``` /// /// [`Client`]: ../../client/struct.Client.html -/// [`Framework`]: struct.Framework.html +/// [`StandardFramework`]: struct.StandardFramework.html +/// [default implementation]: #impl-Default pub struct Configuration { #[doc(hidden)] pub allow_dm: bool, #[doc(hidden)] pub allow_whitespace: bool, @@ -60,6 +63,8 @@ pub struct Configuration { impl Configuration { /// If set to false, bot will ignore any private messages. + /// + /// **Note**: Defaults to `true`. pub fn allow_dm(mut self, allow_dm: bool) -> Self { self.allow_dm = allow_dm; @@ -96,7 +101,9 @@ impl Configuration { self } - /// HashSet of guild Ids where commands will be ignored. + /// HashSet of channels Ids where commands will be working. + /// + /// **Note**: Defaults to an empty HashSet. /// /// # Examples /// @@ -108,19 +115,21 @@ impl Configuration { /// # /// # impl EventHandler for Handler {} /// # let mut client = Client::new("token", Handler).unwrap(); - /// use serenity::model::id::GuildId; + /// use serenity::model::id::ChannelId; /// use serenity::framework::StandardFramework; /// /// client.with_framework(StandardFramework::new().configure(|c| c - /// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect()))); + /// .allowed_channels(vec![ChannelId(7), ChannelId(77)].into_iter().collect()))); /// ``` - pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self { - self.blocked_guilds = guilds; + pub fn allowed_channels(mut self, channels: HashSet<ChannelId>) -> Self { + self.allowed_channels = channels; self } - /// HashSet of channels Ids where commands will be working. + /// HashSet of guild Ids where commands will be ignored. + /// + /// **Note**: Defaults to an empty HashSet. /// /// # Examples /// @@ -132,21 +141,24 @@ impl Configuration { /// # /// # impl EventHandler for Handler {} /// # let mut client = Client::new("token", Handler).unwrap(); - /// use serenity::model::id::ChannelId; + /// use serenity::model::id::GuildId; /// use serenity::framework::StandardFramework; /// /// client.with_framework(StandardFramework::new().configure(|c| c - /// .allowed_channels(vec![ChannelId(7), ChannelId(77)].into_iter().collect()))); + /// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect()))); /// ``` - pub fn allowed_channels(mut self, channels: HashSet<ChannelId>) -> Self { - self.allowed_channels = channels; + pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self { + self.blocked_guilds = guilds; self } /// HashSet of user Ids whose commands will be ignored. + /// /// Guilds owned by user Ids will also be ignored. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Create a HashSet in-place: @@ -169,9 +181,12 @@ impl Configuration { self } - /// The default depth of the message to check for commands. Defaults to 5. + /// The default depth of the message to check for commands. + /// /// This determines how "far" into a message to check for a valid command. /// + /// **Note**: Defaults to 5. + /// /// # Examples /// /// If you set a depth of `1`, and make a command of `"music play"`, but @@ -185,6 +200,8 @@ impl Configuration { /// HashSet of command names that won't be run. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Ignore a set of commands, assuming they exist: @@ -218,6 +235,8 @@ impl Configuration { /// Return `None` to not have a special prefix for the dispatch, and to /// instead use the inherited prefix. /// + /// **Note**: Defaults to no dynamic prefix check. + /// /// # Examples /// /// If the Id of the channel is divisible by 5, return a prefix of `"!"`, @@ -256,6 +275,8 @@ impl Configuration { /// /// For example, if this is set to false, then the bot will respond to any /// other bots including itself. + /// + /// **Note**: Defaults to `true`. pub fn ignore_bots(mut self, ignore_bots: bool) -> Self { self.ignore_bots = ignore_bots; @@ -263,7 +284,8 @@ impl Configuration { } /// If set to true, bot will ignore all commands called by webhooks. - /// True by default. + /// + /// **Note**: Defaults to `true`. pub fn ignore_webhooks(mut self, ignore_webhooks: bool) -> Self { self.ignore_webhooks = ignore_webhooks; @@ -273,7 +295,7 @@ impl Configuration { /// Whether or not to respond to commands initiated with a mention. Note /// that this can be used in conjunction with [`prefix`]. /// - /// By default this is set to `false`. + /// **Note**: Defaults to `false`. /// /// # Examples /// @@ -308,6 +330,8 @@ impl Configuration { /// A `HashSet` of user Ids checks won't apply to. /// + /// **Note**: Defaults to an empty HashSet. + /// /// # Examples /// /// Create a HashSet in-place: @@ -352,6 +376,8 @@ impl Configuration { /// Sets the prefix to respond to. A prefix can be a string slice of any /// non-zero length. /// + /// **Note**: Defaults to an empty vector. + /// /// # Examples /// /// Assign a basic prefix: @@ -377,6 +403,8 @@ impl Configuration { /// Sets the prefixes to respond to. Each can be a string slice of any /// non-zero length. /// + /// **Note**: Refer to [`prefix`] for the default value. + /// /// # Examples /// /// Assign a set of prefixes the bot can respond to: @@ -393,6 +421,8 @@ impl Configuration { /// client.with_framework(StandardFramework::new().configure(|c| c /// .prefixes(vec!["!", ">", "+"]))); /// ``` + /// + /// [`prefix`]: #method.prefix pub fn prefixes<T: ToString, It: IntoIterator<Item=T>>(mut self, prefixes: It) -> Self { self.prefixes = prefixes.into_iter().map(|x| x.to_string()).collect(); @@ -401,7 +431,10 @@ impl Configuration { /// Sets whether command execution can done without a prefix. Works only in private channels. /// + /// **Note**: Defaults to `false`. + /// /// # Note + /// /// Needs the `cache` feature to be enabled. Otherwise this does nothing. pub fn no_dm_prefix(mut self, b: bool) -> Self { self.no_dm_prefix = b; @@ -411,6 +444,8 @@ impl Configuration { /// Sets a delimiter to be used when splitting the content after a command. /// + /// **Note**: Defaults to a vector with a single element of `" "`. + /// /// # Examples /// /// Have the args be seperated by a comma and a space: @@ -436,6 +471,8 @@ impl Configuration { /// Sets multiple delimiters to be used when splitting the content after a command. /// Additionally cleans the default delimiter from the vector. /// + /// **Note**: Refer to [`delimiter`] for the default value. + /// /// # Examples /// /// Have the args be seperated by a comma and a space; and a regular space: @@ -452,6 +489,8 @@ impl Configuration { /// client.with_framework(StandardFramework::new().configure(|c| c /// .delimiters(vec![", ", " "]))); /// ``` + /// + /// [`delimiter`]: #method.delimiter pub fn delimiters<T: ToString, It: IntoIterator<Item=T>>(mut self, delimiters: It) -> Self { self.delimiters.clear(); self.delimiters @@ -460,9 +499,13 @@ impl Configuration { self } - /// Whether the framework shouldn't care about the user's input if it's: `~command`, - /// `~Command`, `~COMMAND`. - /// Setting this to `true` will result in *all* command names to be case insensitive. + /// Whether the framework shouldn't care about the user's input if it's: + /// `~command`, `~Command`, or `~COMMAND`. + /// + /// Setting this to `true` will result in *all* command names to be case + /// insensitive. + /// + /// **Note**: Defaults to `false`. pub fn case_insensitivity(mut self, cs: bool) -> Self { self.case_insensitive = cs; @@ -473,31 +516,40 @@ impl Configuration { impl Default for Configuration { /// Builds a default framework configuration, setting the following: /// + /// - **allow_dm** to `true` /// - **allow_whitespace** to `false` + /// - **allowed_channels** to an empty HashSet + /// - **blocked_guilds** to an empty HashSet + /// - **blocked_users** to an empty HashSet + /// - **case_insensitive** to `false` + /// - **delimiters** to `vec![" "]` /// - **depth** to `5` - /// - **on_mention** to `false` (basically) - /// - **prefix** to `None` + /// - **disabled_commands** to an empty HashSet + /// - **dynamic_prefix** to no dynamic prefix check + /// - **ignore_bots** to `true` + /// - **ignore_webhooks** to `true` /// - **no_dm_prefix** to `false` - /// - **delimiters** to vec![" "] - /// - **case_insensitive** to `false` + /// - **on_mention** to `false` (basically) + /// - **owners** to an empty HashSet + /// - **prefix** to an empty vector fn default() -> Configuration { Configuration { - depth: 5, - on_mention: None, - dynamic_prefix: None, + allow_dm: true, allow_whitespace: false, - prefixes: vec![], - no_dm_prefix: false, - ignore_bots: true, - owners: HashSet::default(), - blocked_users: HashSet::default(), - blocked_guilds: HashSet::default(), allowed_channels: HashSet::default(), - disabled_commands: HashSet::default(), - allow_dm: true, - ignore_webhooks: true, + blocked_guilds: HashSet::default(), + blocked_users: HashSet::default(), case_insensitive: false, delimiters: vec![" ".to_string()], + depth: 5, + disabled_commands: HashSet::default(), + dynamic_prefix: None, + ignore_bots: true, + ignore_webhooks: true, + no_dm_prefix: false, + on_mention: None, + owners: HashSet::default(), + prefixes: vec![], } } } diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index fd74ace..e891ff5 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -266,7 +266,7 @@ pub fn with_embeds<H: BuildHasher>( &help_options.striked_commands_tip_in_dm }; - if let Some(ref striked_command_text) = striked_command_tip { + if let &Some(ref striked_command_text) = striked_command_tip { e = e.colour(help_options.embed_success_colour).description( format!("{}\n{}", &help_options.individual_command_tip, striked_command_text), ); @@ -510,7 +510,7 @@ pub fn plain<H: BuildHasher>( &help_options.striked_commands_tip_in_dm }; - if let Some(ref striked_command_text) = striked_command_tip { + if let &Some(ref striked_command_text) = striked_command_tip { let _ = writeln!(result, "{}\n{}\n", &help_options.individual_command_tip, striked_command_text); } else { let _ = writeln!(result, "{}\n", &help_options.individual_command_tip); diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index d947bfd..86b8c59 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -53,12 +53,12 @@ use client::CACHE; #[cfg(feature = "cache")] use model::channel::Channel; -/// A convenience macro for generating a struct fulfilling the [`Command`] trait. +/// A convenience macro for generating a struct fulfilling the [`Command`][command trait] trait. /// -/// This is meant for use with the [`Framework`], specifically `Framework`::{[`cmd`]/[`command`]}. +/// This is meant for use with the [`StandardFramework`], specifically `Framework`::{[`cmd`]/[`command`]}. /// /// -/// If you're just looking for a simple "register this function as a command", use [`Framework::on`]. +/// If you're just looking for a simple "register this function as a command", use [`StandardFramework::on`]. /// /// # Examples /// @@ -87,11 +87,11 @@ use model::channel::Channel; /// }); /// ``` /// -/// [`Framework`]: framework/index.html -/// [`cmd`]: struct.Framework.html#method.cmd -/// [`command`]: struct.Framework.html#method.command -/// [`Framework::on`]: struct.Framework.html#method.on -/// [`Command`]: trait.Command.html +/// [command trait]: framework/standard/trait.Command.html +/// [`StandardFramework`]: framework/standard/struct.StandardFramework.html +/// [`cmd`]: framework/standard/struct.StandardFramework.html#method.cmd +/// [`command`]: framework/standard/struct.StandardFramework.html#method.command +/// [`StandardFramework::on`]: framework/standard/struct.StandardFramework.html#method.on #[macro_export] macro_rules! command { ($fname:ident($c:ident) $b:block) => { @@ -147,6 +147,17 @@ macro_rules! command { }; } +macro_rules! command_and_help_args { + ($message_content:expr, $position:expr, $command_length:expr, $delimiters:expr) => { + { + let content = $message_content.chars().skip($position).skip_while(|x| x.is_whitespace()) + .skip($command_length).collect::<String>(); + + Args::new(&content.trim(), $delimiters) + } + }; +} + /// An enum representing all possible fail conditions under which a command won't /// be executed. #[derive(Debug)] @@ -215,13 +226,12 @@ pub struct StandardFramework { /// - a command check has been set. /// /// This is used internally to determine whether or not - in addition to - /// dispatching to the [`EventHandler::on_message`] handler - to have the + /// dispatching to the [`EventHandler::message`] handler - to have the /// framework check if a [`Event::MessageCreate`] should be processed by /// itself. /// - /// [`EventHandler::on_message`]: - /// ../client/event_handler/trait.EventHandler.html#method.on_message - /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate + /// [`EventHandler::message`]: ../../client/trait.EventHandler.html#method.message + /// [`Event::MessageCreate`]: ../../model/event/enum.Event.html#variant.MessageCreate pub initialized: bool, user_id: u64, } @@ -255,7 +265,7 @@ impl StandardFramework { /// .prefix("~"))); /// ``` /// - /// [`Client`]: ../client/struct.Client.html + /// [`Client`]: ../../client/struct.Client.html /// [`Configuration::default`]: struct.Configuration.html#method.default /// [`depth`]: struct.Configuration.html#method.depth /// [`prefix`]: struct.Configuration.html#method.prefix @@ -325,12 +335,10 @@ impl StandardFramework { /// /// client.with_framework(StandardFramework::new() /// .complex_bucket("basic", 2, 10, 3, |_, guild_id, channel_id, user_id| { - /// // check if the guild is `123` and the channel where the command(s) was called: - /// // `456` - /// // and if the user who called the command(s) is `789` - /// // otherwise don't apply the bucket at all. - /// guild_id.is_some() && guild_id.unwrap() == 123 && channel_id == 456 - /// && user_id == 789 + /// // Our bucket is very strict. It cannot apply in DMs. + /// // And can only apply if it's in the specific guild, channel and by the specific user. + /// guild_id.is_some() && guild_id.unwrap() == 123 && channel_id == 456 + /// && user_id == 789 /// }) /// .command("ping", |c| c /// .bucket("basic") @@ -338,7 +346,9 @@ impl StandardFramework { /// msg.channel_id.say("pong!")?; /// /// Ok(()) - /// }))); + /// }) + /// ) + /// ); /// ``` /// /// [`bucket`]: #method.bucket @@ -384,14 +394,18 @@ impl StandardFramework { /// /// client.with_framework(StandardFramework::new() /// .complex_bucket("basic", 2, 10, 3, |_, channel_id, user_id| { - /// // check if the channel's id where the command(s) was called is `456` - /// // and if the user who called the command(s) is `789` - /// // otherwise don't apply the bucket at all. + /// Our bucket is somewhat strict. It can only apply in the specific channel and by the specific user. /// channel_id == 456 && user_id == 789 /// }) /// .command("ping", |c| c /// .bucket("basic") - /// .exec_str("pong!"))); + /// .exec(|_, msg, _| { + /// msg.channel_id.say("pong!")?; + /// + /// Ok(()) + /// }) + /// ) + /// ); /// ``` /// /// [`bucket`]: #method.bucket @@ -1033,14 +1047,16 @@ impl Framework for StandardFramework { } let mut check_contains_group_prefix = false; + let mut longest_matching_prefix_len = 0; let to_check = if let Some(ref prefixes) = group.prefixes { // Once `built` starts with a set prefix, // we want to make sure that all following matching prefixes are longer // than the last matching one, this prevents picking a wrong prefix, // e.g. "f" instead of "ferris" due to "f" having a lower index in the `Vec`. - let longest_matching_prefix_len = prefixes.iter().fold(0, |longest_prefix_len, prefix| - if prefix.len() > longest_prefix_len && built.starts_with(prefix) - && (orginal_round.len() == built.len() || command_length > prefix.len() + 1) { + longest_matching_prefix_len = prefixes.iter().fold(0, |longest_prefix_len, prefix| + if prefix.len() > longest_prefix_len + && built.starts_with(prefix) + && (orginal_round.len() == prefix.len() || built.get(prefix.len()..prefix.len() + 1) == Some(" ")) { prefix.len() } else { longest_prefix_len @@ -1060,13 +1076,6 @@ impl Framework for StandardFramework { built.clone() }; - let mut args = { - let content = message.content.chars().skip(position).skip_while(|x| x.is_whitespace()) - .skip(command_length).collect::<String>(); - - Args::new(&content.trim(), &self.configuration.delimiters) - }; - let before = self.before.clone(); let after = self.after.clone(); @@ -1075,6 +1084,8 @@ impl Framework for StandardFramework { if let Some(help) = help { let groups = self.groups.clone(); + let mut args = command_and_help_args!(&message.content, position, command_length, &self.configuration.delimiters); + threadpool.execute(move || { if let Some(before) = before { @@ -1094,12 +1105,12 @@ impl Framework for StandardFramework { } } - if !to_check.is_empty() { if let Some(&CommandOrAlias::Command(ref command)) = group.commands.get(&to_check) { let command = Arc::clone(command); + let mut args = command_and_help_args!(&message.content, position, command_length, &self.configuration.delimiters); if let Some(error) = self.should_fail( &mut context, @@ -1142,12 +1153,16 @@ impl Framework for StandardFramework { } if check_contains_group_prefix { - if let Some(CommandOrAlias::Command(ref command)) = &group.default_command { + + if let &Some(CommandOrAlias::Command(ref command)) = &group.default_command { let command = Arc::clone(command); + let mut args = { + Args::new(&orginal_round[longest_matching_prefix_len..], &self.configuration.delimiters) + }; threadpool.execute(move || { if let Some(before) = before { - if !(before)(&mut context, &message, &built) { + if !(before)(&mut context, &message, &args.full()) { return; } } @@ -1172,7 +1187,7 @@ impl Framework for StandardFramework { } } - if let Some(unrecognised_command) = &self.unrecognised_command { + if let &Some(ref unrecognised_command) = &self.unrecognised_command { let unrecognised_command = unrecognised_command.clone(); threadpool.execute(move || { (unrecognised_command)(&mut context, &message, &unrecognised_command_name); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index ffacba7..07d882c 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -3,9 +3,9 @@ //! //! A shard is an interface for the lower-level receiver and sender. It provides //! what can otherwise be thought of as "sugar methods". A shard represents a -//! single connection to Discord. If acting as a [`Bot`] user, you can make -//! use of a method named "sharding" to have multiple shards, potentially -//! offloading some server load to another server(s). +//! single connection to Discord. You can make use of a method named "sharding" +//! to have multiple shards, potentially offloading some server load to another +//! server(s). //! //! # Sharding //! @@ -38,15 +38,12 @@ //! instance. This should be used when you, for example, want to split 10 shards //! across 3 instances. //! -//! **Note**: User accounts can not shard. Use [`Client::start`]. -//! -//! [`Bot`]: ../enum.LoginType.html#variant.Bot -//! [`Client`]: ../struct.Client.html -//! [`Client::start`]: ../struct.Client.html#method.start -//! [`Client::start_autosharded`]: ../struct.Client.html#method.start_autosharded -//! [`Client::start_shard`]: ../struct.Client.html#method.start_shard -//! [`Client::start_shard_range`]: ../struct.Client.html#method.start_shard_range -//! [`Client::start_shards`]: ../struct.Client.html#method.start_shards +//! [`Client`]: ../client/struct.Client.html +//! [`Client::start`]: ../client/struct.Client.html#method.start +//! [`Client::start_autosharded`]: ../client/struct.Client.html#method.start_autosharded +//! [`Client::start_shard`]: ../client/struct.Client.html#method.start_shard +//! [`Client::start_shard_range`]: ../client/struct.Client.html#method.start_shard_range +//! [`Client::start_shards`]: ../client/struct.Client.html#method.start_shards //! [docs]: https://discordapp.com/developers/docs/topics/gateway#sharding mod error; diff --git a/src/gateway/shard.rs b/src/gateway/shard.rs index 13944e6..09a3b69 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/shard.rs @@ -56,7 +56,7 @@ use websocket::{ /// /// See the documentation for [`new`] on how to use this. /// -/// [`Client`]: ../struct.Client.html +/// [`Client`]: ../client/struct.Client.html /// [`new`]: #method.new /// [`receive`]: #method.receive /// [docs]: https://discordapp.com/developers/docs/topics/gateway#sharding @@ -640,8 +640,8 @@ impl Shard { /// Note that, if the shard is already in a stage of /// [`ConnectionStage::Connecting`], then no action will be performed. /// - /// [`ConnectionStage::Connecting`]: ../../../gateway/enum.ConnectionStage.html#variant.Connecting - /// [`session_id`]: ../../../gateway/struct.Shard.html#method.session_id + /// [`ConnectionStage::Connecting`]: ../gateway/enum.ConnectionStage.html#variant.Connecting + /// [`session_id`]: ../gateway/struct.Shard.html#method.session_id pub fn should_reconnect(&mut self) -> Option<ReconnectType> { if self.stage == ConnectionStage::Connecting { return None; @@ -733,10 +733,9 @@ impl Shard { /// # } /// ``` /// - /// [`Event::GuildMembersChunk`]: - /// ../../model/event/enum.Event.html#variant.GuildMembersChunk - /// [`Guild`]: ../../model/guild/struct.Guild.html - /// [`Member`]: ../../model/guild/struct.Member.html + /// [`Event::GuildMembersChunk`]: ../model/event/enum.Event.html#variant.GuildMembersChunk + /// [`Guild`]: ../model/guild/struct.Guild.html + /// [`Member`]: ../model/guild/struct.Member.html pub fn chunk_guilds<It>( &mut self, guild_ids: It, diff --git a/src/http/mod.rs b/src/http/mod.rs index 71c57c6..d9a6a62 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -20,25 +20,26 @@ //! instance methods where possible, as they each offer different //! levels of a high-level interface to the HTTP module. //! -//! [`Client`]: ../struct.Client.html +//! [`Client`]: ../client/struct.Client.html //! [model]: ../model/index.html pub mod ratelimiting; +pub mod request; +pub mod routing; mod error; -pub use self::error::Error as HttpError; pub use hyper::status::{StatusClass, StatusCode}; +pub use self::error::Error as HttpError; use constants; use hyper::{ client::{ Client as HyperClient, - Request, - RequestBuilder, + Request as HyperRequest, Response as HyperResponse }, - header::ContentType, + header::{ContentType, Headers}, method::Method, mime::{Mime, SubLevel, TopLevel}, net::HttpsConnector, @@ -52,25 +53,35 @@ use internal::prelude::*; use model::prelude::*; use multipart::client::Multipart; use parking_lot::Mutex; -use self::ratelimiting::Route; +use self::{ + request::Request, + routing::RouteInfo, +}; +use serde::de::DeserializeOwned; use serde_json; use std::{ collections::BTreeMap, default::Default, - fmt::Write as FmtWrite, fs::File, io::ErrorKind as IoErrorKind, path::{Path, PathBuf}, sync::Arc }; +lazy_static! { + static ref CLIENT: HyperClient = { + let tc = NativeTlsClient::new().expect("Unable to make http client"); + let connector = HttpsConnector::new(tc); + + HyperClient::with_connector(connector) + }; +} + /// An method used for ratelimiting special routes. /// /// This is needed because `hyper`'s `Method` enum does not derive Copy. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum LightMethod { - /// Indicates that a route is for "any" method. - Any, /// Indicates that a route is for the `DELETE` method only. Delete, /// Indicates that a route is for the `GET` method only. @@ -83,6 +94,18 @@ pub enum LightMethod { Put, } +impl LightMethod { + pub fn hyper_method(&self) -> Method { + match *self { + LightMethod::Delete => Method::Delete, + LightMethod::Get => Method::Get, + LightMethod::Patch => Method::Patch, + LightMethod::Post => Method::Post, + LightMethod::Put => Method::Put, + } + } +} + lazy_static! { static ref TOKEN: Arc<Mutex<String>> = Arc::new(Mutex::new(String::default())); } @@ -121,16 +144,11 @@ pub fn set_token(token: &str) { TOKEN.lock().clone_from(&token.to_string()); } /// [`Group::add_recipient`]: ../model/channel/struct.Group.html#method.add_recipient /// [`User`]: ../model/user/struct.User.html pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::None, - put, - "/channels/{}/recipients/{}", - group_id, - user_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::AddGroupRecipient { group_id, user_id }, + }) } /// Adds a single [`Role`] to a [`Member`] in a [`Guild`]. @@ -141,19 +159,13 @@ pub fn add_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`Guild`]: ../model/guild/struct.Guild.html /// [`Member`]: ../model/guild/struct.Member.html /// [`Role`]: ../model/guild/struct.Role.html -/// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html +/// [Manage Roles]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersIdRolesId(guild_id), - put, - "/guilds/{}/members/{}/roles/{}", - guild_id, - user_id, - role_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::AddMemberRole { guild_id, role_id, user_id }, + }) } /// Bans a [`User`] from a [`Guild`], removing their messages sent in the last @@ -166,20 +178,18 @@ pub fn add_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> /// /// [`Guild`]: ../model/guild/struct.Guild.html /// [`User`]: ../model/user/struct.User.html -/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html +/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &str) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdBansUserId(guild_id), - put, - "/guilds/{}/bans/{}?delete_message_days={}&reason={}", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::GuildBanUser { + delete_message_days: Some(delete_message_days), + reason: Some(reason), guild_id, user_id, - delete_message_days, - reason - ), - ) + }, + }) } /// Ban zeyla from a [`Guild`], removing her messages sent in the last X number @@ -191,7 +201,7 @@ pub fn ban_user(guild_id: u64, user_id: u64, delete_message_days: u8, reason: &s /// **Note**: Requires that you have the [Ban Members] permission. /// /// [`Guild`]: ../model/guild/struct.Guild.html -/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html +/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn ban_zeyla(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> { ban_user(guild_id, 114_941_315_417_899_012, delete_message_days, reason) } @@ -205,7 +215,7 @@ pub fn ban_zeyla(guild_id: u64, delete_message_days: u8, reason: &str) -> Result /// **Note**: Requires that you have the [Ban Members] permission. /// /// [`Guild`]: ../model/guild/struct.Guild.html -/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html +/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn ban_luna(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> { ban_user(guild_id, 180_731_582_049_550_336, delete_message_days, reason) } @@ -219,7 +229,7 @@ pub fn ban_luna(guild_id: u64, delete_message_days: u8, reason: &str) -> Result< /// **Note**: Requires that you have the [Ban Members] permission. /// /// [`Guild`]: ../model/guild/struct.Guild.html -/// [Ban Members]: ../model/permissions/constant.BAN_MEMBERS.html +/// [Ban Members]: ../model/permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> Result<()> { ban_zeyla(guild_id, delete_message_days, reason)?; ban_luna(guild_id, delete_message_days, reason) @@ -235,15 +245,11 @@ pub fn ban_servermoms(guild_id: u64, delete_message_days: u8, reason: &str) -> R /// /// [`Channel`]: ../model/channel/enum.Channel.html pub fn broadcast_typing(channel_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdTyping(channel_id), - post, - "/channels/{}/typing", - channel_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::BroadcastTyping { channel_id }, + }) } /// Creates a [`GuildChannel`] in the [`Guild`] given its Id. @@ -255,41 +261,31 @@ pub fn broadcast_typing(channel_id: u64) -> Result<()> { /// [`Guild`]: ../model/guild/struct.Guild.html /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-channel -/// [Manage Channels]: ../model/permissions/constant.MANAGE_CHANNELS.html +/// [Manage Channels]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS pub fn create_channel(guild_id: u64, map: &Value) -> Result<GuildChannel> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdChannels(guild_id), - post(body), - "/guilds/{}/channels", - guild_id - ); - - serde_json::from_reader::<HyperResponse, GuildChannel>(response) - .map_err(From::from) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateChannel { guild_id }, + }) } /// Creates an emoji in the given [`Guild`] with the given data. /// -/// View the source code for [`Context::create_emoji`] to see what fields this -/// requires. +/// View the source code for [`Guild`]'s [`create_emoji`] method to see what +/// fields this requires. /// /// **Note**: Requires the [Manage Emojis] permission. /// -/// [`Context::create_emoji`]: ../struct.Context.html#method.create_emoji +/// [`create_emoji`]: ../model/guild/struct.Guild.html#method.create_emoji /// [`Guild`]: ../model/guild/struct.Guild.html -/// [Manage Emojis]: ../model/permissions/constant.MANAGE_EMOJIS.html +/// [Manage Emojis]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmojis(guild_id), - post(body), - "/guilds/{}/emojis", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Emoji>(response) - .map_err(From::from) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateEmoji { guild_id }, + }) } /// Creates a guild with the data provided. @@ -329,11 +325,11 @@ pub fn create_emoji(guild_id: u64, map: &Value) -> Result<Emoji> { /// https://discordapp.com/developers/docs/resources/guild#create-guild /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create_guild(map: &Value) -> Result<PartialGuild> { - let body = map.to_string(); - let response = request!(Route::Guilds, post(body), "/guilds"); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateGuild, + }) } /// Creates an [`Integration`] for a [`Guild`]. @@ -344,21 +340,14 @@ pub fn create_guild(map: &Value) -> Result<PartialGuild> { /// /// [`Guild`]: ../model/guild/struct.Guild.html /// [`Integration`]: ../model/guild/struct.Integration.html -/// [Manage Guild]: ../model/permissions/constant.MANAGE_GUILD.html +/// [Manage Guild]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD /// [docs]: https://discordapp.com/developers/docs/resources/guild#create-guild-integration pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) -> Result<()> { - let body = map.to_string(); - - verify( - 204, - request!( - Route::GuildsIdIntegrations(guild_id), - post(body), - "/guilds/{}/integrations/{}", - guild_id, - integration_id - ), - ) + wind(204, Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::CreateGuildIntegration { guild_id, integration_id }, + }) } /// Creates a [`RichInvite`] for the given [channel][`GuildChannel`]. @@ -370,45 +359,39 @@ pub fn create_guild_integration(guild_id: u64, integration_id: u64, map: &Value) /// **Note**: Requires the [Create Invite] permission. /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html -/// [`RichInvite`]: ../model/guild/struct.RichInvite.html -/// [Create Invite]: ../model/permissions/constant.CREATE_INVITE.html +/// [`RichInvite`]: ../model/invite/struct.RichInvite.html +/// [Create Invite]: ../model/permissions/struct.Permissions.html#associatedconstant.CREATE_INVITE /// [docs]: https://discordapp.com/developers/docs/resources/channel#create-channel-invite pub fn create_invite(channel_id: u64, map: &JsonMap) -> Result<RichInvite> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::ChannelsIdInvites(channel_id), - post(body), - "/channels/{}/invites", - channel_id - ); - - serde_json::from_reader::<HyperResponse, RichInvite>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateInvite { channel_id }, + }) } /// Creates a permission override for a member or a role in a channel. pub fn create_permission(channel_id: u64, target_id: u64, map: &Value) -> Result<()> { - let body = map.to_string(); - - verify( - 204, - request!( - Route::ChannelsIdPermissionsOverwriteId(channel_id), - put(body), - "/channels/{}/permissions/{}", - channel_id, - target_id - ), - ) + let body = serde_json::to_vec(map)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreatePermission { channel_id, target_id }, + }) } /// Creates a private channel with a user. pub fn create_private_channel(map: &Value) -> Result<PrivateChannel> { - let body = map.to_string(); - let response = request!(Route::UsersMeChannels, post(body), "/users/@me/channels"); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, PrivateChannel>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::GetUserDmChannels, + }) } /// Reacts to a message. @@ -416,31 +399,26 @@ pub fn create_reaction(channel_id: u64, message_id: u64, reaction_type: &ReactionType) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - put, - "/channels/{}/messages/{}/reactions/{}/@me", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::CreateReaction { + reaction: &reaction_type.as_data(), channel_id, message_id, - reaction_type.as_data() - ), - ) + }, + }) } /// Creates a role. pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsIdRoles(guild_id), - post(body), - "/guilds/{}/roles", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Role>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateRole {guild_id }, + }) } /// Creates a webhook for the given [channel][`GuildChannel`]'s Id, passing in @@ -474,103 +452,77 @@ pub fn create_role(guild_id: u64, map: &JsonMap) -> Result<Role> { /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html pub fn create_webhook(channel_id: u64, map: &Value) -> Result<Webhook> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdWebhooks(channel_id), - post(body), - "/channels/{}/webhooks", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateWebhook { channel_id }, + }) } /// Deletes a private channel or a channel in a guild. pub fn delete_channel(channel_id: u64) -> Result<Channel> { - let response = request!( - Route::ChannelsId(channel_id), - delete, - "/channels/{}", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Channel>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteChannel { channel_id }, + }) } /// Deletes an emoji from a server. pub fn delete_emoji(guild_id: u64, emoji_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdEmojisId(guild_id), - delete, - "/guilds/{}/emojis/{}", - guild_id, - emoji_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteEmoji { guild_id, emoji_id }, + }) } /// Deletes a guild, only if connected account owns it. pub fn delete_guild(guild_id: u64) -> Result<PartialGuild> { - let response = request!(Route::GuildsId(guild_id), delete, "/guilds/{}", guild_id); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteGuild { guild_id }, + }) } /// Remvoes an integration from a guild. pub fn delete_guild_integration(guild_id: u64, integration_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdIntegrationsId(guild_id), - delete, - "/guilds/{}/integrations/{}", - guild_id, - integration_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteGuildIntegration { guild_id, integration_id }, + }) } /// Deletes an invite by code. pub fn delete_invite(code: &str) -> Result<Invite> { - let response = request!(Route::InvitesCode, delete, "/invites/{}", code); - - serde_json::from_reader::<HyperResponse, Invite>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::DeleteInvite { code }, + }) } /// Deletes a message if created by us or we have /// specific permissions. pub fn delete_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesId(LightMethod::Delete, channel_id), - delete, - "/channels/{}/messages/{}", - channel_id, - message_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteMessage { channel_id, message_id }, + }) } /// Deletes a bunch of messages, only works for bots. pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { - let body = map.to_string(); - - verify( - 204, - request!( - Route::ChannelsIdMessagesBulkDelete(channel_id), - post(body), - "/channels/{}/messages/bulk-delete", - channel_id - ), - ) + wind(204, Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::DeleteMessages { channel_id }, + }) } /// Deletes all of the [`Reaction`]s associated with a [`Message`]. @@ -591,30 +543,20 @@ pub fn delete_messages(channel_id: u64, map: &Value) -> Result<()> { /// [`Message`]: ../model/channel/struct.Message.html /// [`Reaction`]: ../model/channel/struct.Reaction.html pub fn delete_message_reactions(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactions(channel_id), - delete, - "/channels/{}/messages/{}/reactions", - channel_id, - message_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteMessageReactions { channel_id, message_id }, + }) } /// Deletes a permission override from a role or a member in a channel. pub fn delete_permission(channel_id: u64, target_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPermissionsOverwriteId(channel_id), - delete, - "/channels/{}/permissions/{}", - channel_id, - target_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeletePermission { channel_id, target_id }, + }) } /// Deletes a reaction from a message if owned by us or @@ -628,32 +570,25 @@ pub fn delete_reaction(channel_id: u64, .map(|uid| uid.to_string()) .unwrap_or_else(|| "@me".to_string()); - verify( - 204, - request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - delete, - "/channels/{}/messages/{}/reactions/{}/{}", + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteReaction { + reaction: &reaction_type.as_data(), + user: &user, channel_id, message_id, - reaction_type.as_data(), - user - ), - ) + }, + }) } /// Deletes a role from a server. Can't remove the default everyone role. pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdRolesId(guild_id), - delete, - "/guilds/{}/roles/{}", - guild_id, - role_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteRole { guild_id, role_id }, + }) } /// Deletes a [`Webhook`] given its Id. @@ -666,7 +601,7 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { /// Deletes a webhook given its Id: /// /// ```rust,no_run -/// use serenity::{Client, http}; +/// use serenity::http; /// use std::env; /// /// // Due to the `delete_webhook` function requiring you to authenticate, you @@ -679,15 +614,11 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { /// [`Webhook`]: ../model/webhook/struct.Webhook.html /// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html pub fn delete_webhook(webhook_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::WebhooksId(webhook_id), - delete, - "/webhooks/{}", - webhook_id, - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteWebhook { webhook_id }, + }) } /// Deletes a [`Webhook`] given its Id and unique token. @@ -709,123 +640,93 @@ pub fn delete_webhook(webhook_id: u64) -> Result<()> { /// /// [`Webhook`]: ../model/webhook/struct.Webhook.html pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> { - let client = request_client!(); - - verify( - 204, - retry(|| { - client - .delete(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - }).map_err(Error::Hyper)?, - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::DeleteWebhookWithToken { token, webhook_id }, + }) } /// Changes channel information. pub fn edit_channel(channel_id: u64, map: &JsonMap) -> Result<GuildChannel> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::ChannelsId(channel_id), - patch(body), - "/channels/{}", - channel_id - ); - - serde_json::from_reader::<HyperResponse, GuildChannel>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditChannel {channel_id }, + }) } /// Changes emoji information. pub fn edit_emoji(guild_id: u64, emoji_id: u64, map: &Value) -> Result<Emoji> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmojisId(guild_id), - patch(body), - "/guilds/{}/emojis/{}", - guild_id, - emoji_id - ); - - serde_json::from_reader::<HyperResponse, Emoji>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditEmoji { guild_id, emoji_id }, + }) } /// Changes guild information. pub fn edit_guild(guild_id: u64, map: &JsonMap) -> Result<PartialGuild> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsId(guild_id), - patch(body), - "/guilds/{}", - guild_id - ); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuild { guild_id }, + }) } /// Edits the positions of a guild's channels. pub fn edit_guild_channel_positions(guild_id: u64, value: &Value) -> Result<()> { - let body = serde_json::to_string(value)?; - - verify( - 204, - request!( - Route::GuildsIdChannels(guild_id), - patch(body), - "/guilds/{}/channels", - guild_id, - ), - ) + let body = serde_json::to_vec(value)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuildChannels { guild_id }, + }) } /// Edits a [`Guild`]'s embed setting. /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn edit_guild_embed(guild_id: u64, map: &Value) -> Result<GuildEmbed> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdEmbed(guild_id), - patch(body), - "/guilds/{}/embed", - guild_id - ); - - serde_json::from_reader::<HyperResponse, GuildEmbed>(response) - .map_err(From::from) + let body = serde_json::to_vec(map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditGuildEmbed { guild_id }, + }) } /// Does specific actions to a member. pub fn edit_member(guild_id: u64, user_id: u64, map: &JsonMap) -> Result<()> { - let body = serde_json::to_string(map)?; - - verify( - 204, - request!( - Route::GuildsIdMembersId(guild_id), - patch(body), - "/guilds/{}/members/{}", - guild_id, - user_id - ), - ) + let body = serde_json::to_vec(map)?; + + wind(204, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditMember { guild_id, user_id }, + }) } /// Edits a message by Id. /// /// **Note**: Only the author of a message can modify it. pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Message> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - patch(body), - "/channels/{}/messages/{}", - channel_id, - message_id - ); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditMessage { channel_id, message_id }, + }) } /// Edits the current user's nickname for the provided [`Guild`] via its Id. @@ -835,15 +736,13 @@ pub fn edit_message(channel_id: u64, message_id: u64, map: &Value) -> Result<Mes /// [`Guild`]: ../model/guild/struct.Guild.html pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { let map = json!({ "nick": new_nickname }); - let body = map.to_string(); - let response = request!( - Route::GuildsIdMembersMeNick(guild_id), - patch(body), - "/guilds/{}/members/@me/nick", - guild_id - ); + let body = serde_json::to_vec(&map)?; - verify(200, response) + wind(200, Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditNickname { guild_id }, + }) } /// Edits the current user's profile settings. @@ -859,8 +758,13 @@ pub fn edit_nickname(guild_id: u64, new_nickname: Option<&str>) -> Result<()> { /// change and when the token is internally changed to be invalid requests, as /// the token may be outdated. pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { - let body = serde_json::to_string(map)?; - let response = request!(Route::UsersMe, patch(body), "/users/@me"); + let body = serde_json::to_vec(map)?; + + let response = request(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditProfile, + })?; let mut value = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -872,41 +776,32 @@ pub fn edit_profile(map: &JsonMap) -> Result<CurrentUser> { } } - serde_json::from_value::<CurrentUser>(value) - .map_err(From::from) + serde_json::from_value::<CurrentUser>(value).map_err(From::from) } /// Changes a role in a guild. pub fn edit_role(guild_id: u64, role_id: u64, map: &JsonMap) -> Result<Role> { - let body = serde_json::to_string(map)?; - let response = request!( - Route::GuildsIdRolesId(guild_id), - patch(body), - "/guilds/{}/roles/{}", - guild_id, - role_id - ); - - serde_json::from_reader::<HyperResponse, Role>(response) - .map_err(From::from) + let body = serde_json::to_vec(&map)?; + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditRole { guild_id, role_id }, + }) } /// Changes the position of a role in a guild. pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result<Vec<Role>> { - let body = serde_json::to_string(&json!({ + let body = serde_json::to_vec(&json!({ "id": role_id, "position": position, }))?; - let response = request!( - Route::GuildsIdRolesId(guild_id), - patch(body), - "/guilds/{}/roles/{}", - guild_id, - role_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Role>>(response) - .map_err(From::from) + + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditRole { guild_id, role_id }, + }) } /// Edits a the webhook with the given data. @@ -949,16 +844,11 @@ pub fn edit_role_position(guild_id: u64, role_id: u64, position: u64) -> Result< // The tests are ignored, rather than no_run'd, due to rustdoc tests with // external crates being incredibly messy and misleading in the end user's view. pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { - let body = map.to_string(); - let response = request!( - Route::WebhooksId(webhook_id), - patch(body), - "/webhooks/{}", - webhook_id, - ); - - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + fire(Request { + body: Some(map.to_string().as_bytes()), + headers: None, + route: RouteInfo::EditWebhook { webhook_id }, + }) } /// Edits the webhook with the given data. @@ -988,17 +878,13 @@ pub fn edit_webhook(webhook_id: u64, map: &Value) -> Result<Webhook> { /// /// [`edit_webhook`]: fn.edit_webhook.html pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: &JsonMap) -> Result<Webhook> { - let body = serde_json::to_string(map)?; - let client = request_client!(); + let body = serde_json::to_vec(map)?; - let response = retry(|| { - client - .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - .body(&body) - }).map_err(Error::Hyper)?; - - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::EditWebhookWithToken { token, webhook_id }, + }) } /// Executes a webhook, posting a [`Message`] in the webhook's associated @@ -1066,23 +952,18 @@ pub fn execute_webhook(webhook_id: u64, wait: bool, map: &JsonMap) -> Result<Option<Message>> { - let body = serde_json::to_string(map)?; - - let client = request_client!(); - - let response = retry(|| { - client - .post(&format!( - api!("/webhooks/{}/{}?wait={}"), - webhook_id, - token, - wait - )) - .body(&body) - .header(ContentType( - Mime(TopLevel::Application, SubLevel::Json, vec![]), - )) - }).map_err(Error::Hyper)?; + let body = serde_json::to_vec(map)?; + + let mut headers = Headers::new(); + headers.set(ContentType( + Mime(TopLevel::Application, SubLevel::Json, vec![]), + )); + + let response = request(Request { + body: Some(&body), + headers: Some(headers), + route: RouteInfo::ExecuteWebhook { token, wait, webhook_id }, + })?; if response.status == StatusCode::NoContent { return Ok(None); @@ -1097,10 +978,10 @@ pub fn execute_webhook(webhook_id: u64, /// /// Does not require authentication. pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { - let client = request_client!(); - - let response = retry(|| { - client.get(status!("/scheduled-maintenances/active.json")) + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetActiveMaintenance, })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1114,15 +995,11 @@ pub fn get_active_maintenances() -> Result<Vec<Maintenance>> { /// Gets all the users that are banned in specific guild. pub fn get_bans(guild_id: u64) -> Result<Vec<Ban>> { - let response = request!( - Route::GuildsIdBans(guild_id), - get, - "/guilds/{}/bans", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Ban>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetBans { guild_id }, + }) } /// Gets all audit logs in a specific guild. @@ -1131,57 +1008,35 @@ pub fn get_audit_logs(guild_id: u64, user_id: Option<u64>, before: Option<u64>, limit: Option<u8>) -> Result<AuditLogs> { - let mut params = Vec::with_capacity(4); - - if let Some(action_type) = action_type { - params.push(format!("action_type={}", action_type)); - } - if let Some(user_id) = user_id { - params.push(format!("user_id={}", user_id)); - } - if let Some(before) = before { - params.push(format!("before={}", before)); - } - if let Some(limit) = limit { - params.push(format!("limit={}", limit)); - } - - let mut query_string = params.join("&"); - if !query_string.is_empty() { - query_string.insert(0, '?'); - } - - let response = request!( - Route::GuildsIdAuditLogs(guild_id), - get, - "/guilds/{}/audit-logs{}", - guild_id, - query_string - ); - - serde_json::from_reader::<HyperResponse, AuditLogs>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + }, + }) } /// Gets current bot gateway. pub fn get_bot_gateway() -> Result<BotGateway> { - let response = request!(Route::GatewayBot, get, "/gateway/bot"); - - serde_json::from_reader::<HyperResponse, BotGateway>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetBotGateway, + }) } /// Gets all invites for a channel. pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { - let response = request!( - Route::ChannelsIdInvites(channel_id), - get, - "/channels/{}/invites", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannelInvites { channel_id }, + }) } /// Retrieves the webhooks for the given [channel][`GuildChannel`]'s Id. @@ -1203,114 +1058,94 @@ pub fn get_channel_invites(channel_id: u64) -> Result<Vec<RichInvite>> { /// /// [`GuildChannel`]: ../model/channel/struct.GuildChannel.html pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> { - let response = request!( - Route::ChannelsIdWebhooks(channel_id), - get, - "/channels/{}/webhooks", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannelWebhooks { channel_id }, + }) } /// Gets channel information. pub fn get_channel(channel_id: u64) -> Result<Channel> { - let response = request!( - Route::ChannelsId(channel_id), - get, - "/channels/{}", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Channel>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannel { channel_id }, + }) } /// Gets all channels in a guild. pub fn get_channels(guild_id: u64) -> Result<Vec<GuildChannel>> { - let response = request!( - Route::ChannelsId(guild_id), - get, - "/guilds/{}/channels", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<GuildChannel>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetChannels { guild_id }, + }) } /// Gets information about the current application. /// /// **Note**: Only applications may use this endpoint. pub fn get_current_application_info() -> Result<CurrentApplicationInfo> { - let response = request!(Route::None, get, "/oauth2/applications/@me"); - - serde_json::from_reader::<HyperResponse, CurrentApplicationInfo>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetCurrentApplicationInfo, + }) } /// Gets information about the user we're connected with. pub fn get_current_user() -> Result<CurrentUser> { - let response = request!(Route::UsersMe, get, "/users/@me"); - - serde_json::from_reader::<HyperResponse, CurrentUser>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetCurrentUser, + }) } /// Gets current gateway. pub fn get_gateway() -> Result<Gateway> { - let response = request!(Route::Gateway, get, "/gateway"); - - serde_json::from_reader::<HyperResponse, Gateway>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGateway, + }) } /// Gets guild information. pub fn get_guild(guild_id: u64) -> Result<PartialGuild> { - let response = request!(Route::GuildsId(guild_id), get, "/guilds/{}", guild_id); - - serde_json::from_reader::<HyperResponse, PartialGuild>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuild { guild_id }, + }) } /// Gets a guild embed information. pub fn get_guild_embed(guild_id: u64) -> Result<GuildEmbed> { - let response = request!( - Route::GuildsIdEmbed(guild_id), - get, - "/guilds/{}/embeds", - guild_id - ); - - serde_json::from_reader::<HyperResponse, GuildEmbed>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildEmbed { guild_id }, + }) } /// Gets integrations that a guild has. pub fn get_guild_integrations(guild_id: u64) -> Result<Vec<Integration>> { - let response = request!( - Route::GuildsIdIntegrations(guild_id), - get, - "/guilds/{}/integrations", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Integration>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildIntegrations { guild_id }, + }) } /// Gets all invites to a guild. pub fn get_guild_invites(guild_id: u64) -> Result<Vec<RichInvite>> { - let response = request!( - Route::GuildsIdInvites(guild_id), - get, - "/guilds/{}/invites", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<RichInvite>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildInvites { guild_id }, + }) } /// Gets a guild's vanity URL if it has one. @@ -1320,12 +1155,11 @@ pub fn get_guild_vanity_url(guild_id: u64) -> Result<String> { code: String, } - let response = request!( - Route::GuildsIdVanityUrl(guild_id), - get, - "/guilds/{}/vanity-url", - guild_id - ); + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildVanityUrl { guild_id }, + })?; serde_json::from_reader::<HyperResponse, GuildVanityUrl>(response) .map(|x| x.code) @@ -1338,14 +1172,11 @@ pub fn get_guild_members(guild_id: u64, limit: Option<u64>, after: Option<u64>) -> Result<Vec<Member>> { - let response = request!( - Route::GuildsIdMembers(guild_id), - get, - "/guilds/{}/members?limit={}&after={}", - guild_id, - limit.unwrap_or(500), - after.unwrap_or(0) - ); + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildMembers { after, guild_id, limit }, + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1364,45 +1195,43 @@ pub fn get_guild_members(guild_id: u64, /// Gets the amount of users that can be pruned. pub fn get_guild_prune_count(guild_id: u64, map: &Value) -> Result<GuildPrune> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdPrune(guild_id), - get(body), - "/guilds/{}/prune", - guild_id - ); - - serde_json::from_reader::<HyperResponse, GuildPrune>(response) - .map_err(From::from) + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct GetGuildPruneCountRequest { + days: u64, + } + + let req = serde_json::from_value::<GetGuildPruneCountRequest>(map.clone())?; + + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildPruneCount { + days: req.days, + guild_id, + }, + }) } /// Gets regions that a guild can use. If a guild has the `VIP_REGIONS` feature /// enabled, then additional VIP-only regions are returned. pub fn get_guild_regions(guild_id: u64) -> Result<Vec<VoiceRegion>> { - let response = request!( - Route::GuildsIdRegions(guild_id), - get, - "/guilds/{}/regions", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildRegions { guild_id }, + }) } /// Retrieves a list of roles in a [`Guild`]. /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { - let response = request!( - Route::GuildsIdRoles(guild_id), - get, - "/guilds/{}/roles", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Role>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildRoles { guild_id }, + }) } /// Retrieves the webhooks for the given [guild][`Guild`]'s Id. @@ -1424,15 +1253,11 @@ pub fn get_guild_roles(guild_id: u64) -> Result<Vec<Role>> { /// /// [`Guild`]: ../model/guild/struct.Guild.html pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { - let response = request!( - Route::GuildsIdWebhooks(guild_id), - get, - "/guilds/{}/webhooks", - guild_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Webhook>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuildWebhooks { guild_id }, + }) } /// Gets a paginated list of the current user's guilds. @@ -1456,54 +1281,40 @@ pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { /// /// [docs]: https://discordapp.com/developers/docs/resources/user#get-current-user-guilds pub fn get_guilds(target: &GuildPagination, limit: u64) -> Result<Vec<GuildInfo>> { - let mut uri = format!("/users/@me/guilds?limit={}", limit); - - match *target { - GuildPagination::After(id) => { - write!(uri, "&after={}", id)?; - }, - GuildPagination::Before(id) => { - write!(uri, "&before={}", id)?; - }, - } - - let response = request!(Route::UsersMeGuilds, get, "{}", uri); + let (after, before) = match *target { + GuildPagination::After(id) => (Some(id.0), None), + GuildPagination::Before(id) => (None, Some(id.0)), + }; - serde_json::from_reader::<HyperResponse, Vec<GuildInfo>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetGuilds { after, before, limit }, + }) } /// Gets information about a specific invite. #[allow(unused_mut)] -pub fn get_invite(code: &str, stats: bool) -> Result<Invite> { - let mut invite = code; - +pub fn get_invite(mut code: &str, stats: bool) -> Result<Invite> { #[cfg(feature = "utils")] { - invite = ::utils::parse_invite(invite); - } - - let mut uri = format!("/invites/{}", invite); - - if stats { - uri.push_str("?with_counts=true"); + code = ::utils::parse_invite(code); } - let response = request!(Route::InvitesCode, get, "{}", uri); - - serde_json::from_reader::<HyperResponse, Invite>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetInvite { code, stats }, + }) } /// Gets member of a guild. pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { - let response = request!( - Route::GuildsIdMembersId(guild_id), - get, - "/guilds/{}/members/{}", - guild_id, - user_id - ); + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetMember { guild_id, user_id }, + })?; let mut v = serde_json::from_reader::<HyperResponse, Value>(response)?; @@ -1516,40 +1327,32 @@ pub fn get_member(guild_id: u64, user_id: u64) -> Result<Member> { /// Gets a message by an Id, bots only. pub fn get_message(channel_id: u64, message_id: u64) -> Result<Message> { - let response = request!( - Route::ChannelsIdMessagesId(LightMethod::Any, channel_id), - get, - "/channels/{}/messages/{}", - channel_id, - message_id - ); - - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetMessage { channel_id, message_id }, + }) } /// Gets X messages from a channel. pub fn get_messages(channel_id: u64, query: &str) -> Result<Vec<Message>> { - let url = format!(api!("/channels/{}/messages{}"), channel_id, query); - let client = request_client!(); - - let response = request(Route::ChannelsIdMessages(channel_id), || client.get(&url))?; - - serde_json::from_reader::<HyperResponse, Vec<Message>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetMessages { + query: query.to_owned(), + channel_id, + }, + }) } /// Gets all pins of a channel. pub fn get_pins(channel_id: u64) -> Result<Vec<Message>> { - let response = request!( - Route::ChannelsIdPins(channel_id), - get, - "/channels/{}/pins", - channel_id - ); - - serde_json::from_reader::<HyperResponse, Vec<Message>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetPins { channel_id }, + }) } /// Gets user Ids based on their reaction to a message. This endpoint is dumb. @@ -1559,36 +1362,30 @@ pub fn get_reaction_users(channel_id: u64, limit: u8, after: Option<u64>) -> Result<Vec<User>> { - let mut uri = format!( - "/channels/{}/messages/{}/reactions/{}?limit={}", - channel_id, - message_id, - reaction_type.as_data(), - limit - ); - - if let Some(user_id) = after { - write!(uri, "&after={}", user_id)?; - } - - let response = request!( - Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), - get, - "{}", - uri - ); + let reaction = reaction_type.as_data(); - serde_json::from_reader::<HyperResponse, Vec<User>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + reaction, + }, + }) } /// Gets the current unresolved incidents from Discord's Status API. /// /// Does not require authentication. pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { - let client = request_client!(); - - let response = retry(|| client.get(status!("/incidents/unresolved.json")))?; + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetUnresolvedIncidents, + })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1603,10 +1400,10 @@ pub fn get_unresolved_incidents() -> Result<Vec<Incident>> { /// /// Does not require authentication. pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { - let client = request_client!(); - - let response = retry(|| { - client.get(status!("/scheduled-maintenances/upcoming.json")) + let response = request(Request { + body: None, + headers: None, + route: RouteInfo::GetUpcomingMaintenances, })?; let mut map: BTreeMap<String, Value> = serde_json::from_reader(response)?; @@ -1620,26 +1417,29 @@ pub fn get_upcoming_maintenances() -> Result<Vec<Maintenance>> { /// Gets a user by Id. pub fn get_user(user_id: u64) -> Result<User> { - let response = request!(Route::UsersId, get, "/users/{}", user_id); - - serde_json::from_reader::<HyperResponse, User>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetUser { user_id }, + }) } /// Gets our DM channels. pub fn get_user_dm_channels() -> Result<Vec<PrivateChannel>> { - let response = request!(Route::UsersMeChannels, get, "/users/@me/channels"); - - serde_json::from_reader::<HyperResponse, Vec<PrivateChannel>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetUserDmChannels, + }) } /// Gets all voice regions. pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { - let response = request!(Route::VoiceRegions, get, "/voice/regions"); - - serde_json::from_reader::<HyperResponse, Vec<VoiceRegion>>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetVoiceRegions, + }) } /// Retrieves a webhook given its Id. @@ -1660,15 +1460,11 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { /// /// [`get_webhook_with_token`]: fn.get_webhook_with_token.html pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { - let response = request!( - Route::WebhooksId(webhook_id), - get, - "/webhooks/{}", - webhook_id, - ); - - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetWebhook { webhook_id }, + }) } /// Retrieves a webhook given its Id and unique token. @@ -1689,64 +1485,47 @@ pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { /// .expect("Error getting webhook"); /// ``` pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> { - let client = request_client!(); - - let response = retry(|| { - client - .get(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) - }).map_err(Error::Hyper)?; - - serde_json::from_reader::<HyperResponse, Webhook>(response) - .map_err(From::from) + fire(Request { + body: None, + headers: None, + route: RouteInfo::GetWebhookWithToken { token, webhook_id }, + }) } /// Kicks a member from a guild. pub fn kick_member(guild_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersId(guild_id), - delete, - "/guilds/{}/members/{}", - guild_id, - user_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::KickMember { guild_id, user_id }, + }) } /// Leaves a group DM. -pub fn leave_group(guild_id: u64) -> Result<Group> { - let response = request!(Route::None, delete, "/channels/{}", guild_id); - - serde_json::from_reader::<HyperResponse, Group>(response) - .map_err(From::from) +pub fn leave_group(group_id: u64) -> Result<Group> { + fire(Request { + body: None, + headers: None, + route: RouteInfo::LeaveGroup { group_id }, + }) } /// Leaves a guild. pub fn leave_guild(guild_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::UsersMeGuildsId, - delete, - "/users/@me/guilds/{}", - guild_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::LeaveGuild { guild_id }, + }) } /// Deletes a user from group DM. pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::None, - delete, - "/channels/{}/recipients/{}", - group_id, - user_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveGroupRecipient { group_id, user_id }, + }) } /// Sends file(s) to a channel. @@ -1760,7 +1539,7 @@ pub fn remove_group_recipient(group_id: u64, user_id: u64) -> Result<()> { /// [`HttpError::InvalidRequest`]: enum.HttpError.html#variant.InvalidRequest pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, map: JsonMap) -> Result<Message> where T: Into<AttachmentType<'a>> { - let uri = format!(api!("/channels/{}/messages"), channel_id); + let uri = api!("/channels/{}/messages", channel_id); let url = match Url::parse(&uri) { Ok(url) => url, Err(_) => return Err(Error::Url(uri)), @@ -1768,7 +1547,7 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m let tc = NativeTlsClient::new()?; let connector = HttpsConnector::new(tc); - let mut request = Request::with_connector(Method::Post, url, &connector)?; + let mut request = HyperRequest::with_connector(Method::Post, url, &connector)?; request .headers_mut() .set(header::Authorization(TOKEN.lock().clone())); @@ -1817,50 +1596,36 @@ pub fn send_files<'a, T, It: IntoIterator<Item=T>>(channel_id: u64, files: It, m return Err(Error::Http(HttpError::UnsuccessfulRequest(response))); } - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + serde_json::from_reader(response).map_err(From::from) } /// Sends a message to a channel. pub fn send_message(channel_id: u64, map: &Value) -> Result<Message> { - let body = map.to_string(); - let response = request!( - Route::ChannelsIdMessages(channel_id), - post(body), - "/channels/{}/messages", - channel_id - ); + let body = serde_json::to_vec(map)?; - serde_json::from_reader::<HyperResponse, Message>(response) - .map_err(From::from) + fire(Request { + body: Some(&body), + headers: None, + route: RouteInfo::CreateMessage { channel_id }, + }) } /// Pins a message in a channel. pub fn pin_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPinsMessageId(channel_id), - put, - "/channels/{}/pins/{}", - channel_id, - message_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::PinMessage { channel_id, message_id }, + }) } /// Unbans a user from a guild. pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdBansUserId(guild_id), - delete, - "/guilds/{}/bans/{}", - guild_id, - user_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveBan { guild_id, user_id }, + }) } /// Deletes a single [`Role`] from a [`Member`] in a [`Guild`]. @@ -1871,69 +1636,154 @@ pub fn remove_ban(guild_id: u64, user_id: u64) -> Result<()> { /// [`Guild`]: ../model/guild/struct.Guild.html /// [`Member`]: ../model/guild/struct.Member.html /// [`Role`]: ../model/guild/struct.Role.html -/// [Manage Roles]: ../model/permissions/constant.MANAGE_ROLES.html +/// [Manage Roles]: ../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES pub fn remove_member_role(guild_id: u64, user_id: u64, role_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdMembersIdRolesId(guild_id), - delete, - "/guilds/{}/members/{}/roles/{}", - guild_id, - user_id, - role_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::RemoveMemberRole { guild_id, user_id, role_id }, + }) } /// Starts removing some members from a guild based on the last time they've been online. pub fn start_guild_prune(guild_id: u64, map: &Value) -> Result<GuildPrune> { - let body = map.to_string(); - let response = request!( - Route::GuildsIdPrune(guild_id), - post(body), - "/guilds/{}/prune", - guild_id - ); - - serde_json::from_reader::<HyperResponse, GuildPrune>(response) - .map_err(From::from) + // Note for 0.6.x: turn this into a function parameter. + #[derive(Deserialize)] + struct StartGuildPruneRequest { + days: u64, + } + + let req = serde_json::from_value::<StartGuildPruneRequest>(map.clone())?; + + fire(Request { + body: None, + headers: None, + route: RouteInfo::StartGuildPrune { + days: req.days, + guild_id, + }, + }) } /// Starts syncing an integration with a guild. pub fn start_integration_sync(guild_id: u64, integration_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::GuildsIdIntegrationsIdSync(guild_id), - post, - "/guilds/{}/integrations/{}/sync", - guild_id, - integration_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::StartIntegrationSync { guild_id, integration_id }, + }) } /// Unpins a message from a channel. pub fn unpin_message(channel_id: u64, message_id: u64) -> Result<()> { - verify( - 204, - request!( - Route::ChannelsIdPinsMessageId(channel_id), - delete, - "/channels/{}/pins/{}", - channel_id, - message_id - ), - ) + wind(204, Request { + body: None, + headers: None, + route: RouteInfo::UnpinMessage { channel_id, message_id }, + }) } -fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse> - where F: Fn() -> RequestBuilder<'a> { - let response = ratelimiting::perform(route, || { - f().header(header::Authorization(TOKEN.lock().clone())) - .header(header::ContentType::json()) - })?; +/// Fires off a request, deserializing the response reader via the given type +/// bound. +/// +/// If you don't need to deserialize the response and want the response instance +/// itself, use [`request`]. +/// +/// # Examples +/// +/// Create a new message via the [`RouteInfo::CreateMessage`] endpoint and +/// deserialize the response into a [`Message`]: +/// +/// ```rust,no_run +/// # extern crate serenity; +/// # +/// # use std::error::Error; +/// # +/// # fn try_main() -> Result<(), Box<Error>> { +/// # +/// use serenity::{ +/// http::{ +/// self, +/// request::RequestBuilder, +/// routing::RouteInfo, +/// }, +/// model::channel::Message, +/// }; +/// +/// let bytes = vec![ +/// // payload bytes here +/// ]; +/// let channel_id = 381880193700069377; +/// let route_info = RouteInfo::CreateMessage { channel_id }; +/// +/// let mut request = RequestBuilder::new(route_info); +/// request.body(Some(&bytes)); +/// +/// let message = http::fire::<Message>(request.build())?; +/// +/// println!("Message content: {}", message.content); +/// # +/// # Ok(()) +/// # } +/// # +/// # fn main() { +/// # try_main().unwrap(); +/// # } +/// ``` +/// +/// [`request`]: fn.request.html +pub fn fire<T: DeserializeOwned>(req: Request) -> Result<T> { + let response = request(req)?; + + serde_json::from_reader(response).map_err(From::from) +} + +/// Performs a request, ratelimiting it if necessary. +/// +/// Returns the raw hyper Response. Use [`fire`] to deserialize the response +/// into some type. +/// +/// # Examples +/// +/// Send a body of bytes over the [`RouteInfo::CreateMessage`] endpoint: +/// +/// ```rust,no_run +/// # extern crate serenity; +/// # +/// # use std::error::Error; +/// # +/// # fn try_main() -> Result<(), Box<Error>> { +/// # +/// use serenity::http::{ +/// self, +/// request::RequestBuilder, +/// routing::RouteInfo, +/// }; +/// +/// let bytes = vec![ +/// // payload bytes here +/// ]; +/// let channel_id = 381880193700069377; +/// let route_info = RouteInfo::CreateMessage { channel_id }; +/// +/// let mut request = RequestBuilder::new(route_info); +/// request.body(Some(&bytes)); +/// +/// let response = http::request(request.build())?; +/// +/// println!("Response successful?: {}", response.status.is_success()); +/// # +/// # Ok(()) +/// # } +/// # +/// # fn main() { +/// # try_main().unwrap(); +/// # } +/// ``` +/// +/// [`fire`]: fn.fire.html +pub fn request(req: Request) -> Result<HyperResponse> { + let response = ratelimiting::perform(req)?; if response.status.class() == StatusClass::Success { Ok(response) @@ -1942,28 +1792,37 @@ fn request<'a, F>(route: Route, f: F) -> Result<HyperResponse> } } -pub(crate) fn retry<'a, F>(f: F) -> HyperResult<HyperResponse> - where F: Fn() -> RequestBuilder<'a> { - let req = || { - f().header(header::UserAgent(constants::USER_AGENT.to_string())) - .send() - }; - - match req() { - Err(HyperError::Io(ref io)) if io.kind() == IoErrorKind::ConnectionAborted => req(), - other => other, +fn retry(request: &Request) -> HyperResult<HyperResponse> { + // Retry the request twice in a loop until it succeeds. + // + // If it doesn't and the loop breaks, try one last time. + for _ in 0..3 { + match request.build().send() { + Err(HyperError::Io(ref io)) + if io.kind() == IoErrorKind::ConnectionAborted => continue, + other => return other, + } } + + request.build().send() } -fn verify(expected: u16, response: HyperResponse) -> Result<()> { - if response.status.to_u16() == expected { +/// Performs a request and then verifies that the response status code is equal +/// to the expected value. +/// +/// This is a function that performs a light amount of work and returns an +/// empty tuple, so it's called "wind" to denote that it's lightweight. +fn wind(expected: u16, req: Request) -> Result<()> { + let resp = request(req)?; + + if resp.status.to_u16() == expected { return Ok(()); } - debug!("Expected {}, got {}", expected, response.status); - trace!("Unsuccessful response: {:?}", response); + debug!("Expected {}, got {}", expected, resp.status); + trace!("Unsuccessful response: {:?}", resp); - Err(Error::Http(HttpError::UnsuccessfulRequest(response))) + Err(Error::Http(HttpError::UnsuccessfulRequest(resp))) } /// Enum that allows a user to pass a `Path` or a `File` type to `send_files` @@ -2008,3 +1867,21 @@ pub enum GuildPagination { /// The Id to get the guilds before. Before(GuildId), } + +#[cfg(test)] +mod test { + use super::AttachmentType; + use std::path::Path; + + #[test] + fn test_attachment_type() { + assert!(match AttachmentType::from(Path::new("./dogs/corgis/kona.png")) { + AttachmentType::Path(_) => true, + _ => false, + }); + assert!(match AttachmentType::from("./cats/copycat.png") { + AttachmentType::Path(_) => true, + _ => false, + }); + } +} diff --git a/src/http/ratelimiting.rs b/src/http/ratelimiting.rs index b8152d1..298ca6f 100644 --- a/src/http/ratelimiting.rs +++ b/src/http/ratelimiting.rs @@ -8,7 +8,7 @@ //! > For example, `/channels/:channel_id` and //! > `/channels/:channel_id/messages/:message_id` both take `channel_id` into //! > account when generating rate limits since it's the major parameter. The -//! only current major parameters are `channel_id` and `guild_id`. +//! only current major parameters are `channel_id`, `guild_id` and `webhook_id`. //! //! This results in the two URIs of `GET /channels/4/messages/7` and //! `GET /channels/5/messages/8` being rate limited _separately_. However, the @@ -40,8 +40,10 @@ //! [Taken from]: https://discordapp.com/developers/docs/topics/rate-limits#rate-limits #![allow(zero_ptr)] +pub use super::routing::Route; + use chrono::{DateTime, Utc}; -use hyper::client::{RequestBuilder, Response}; +use hyper::client::Response; use hyper::header::Headers; use hyper::status::StatusCode; use internal::prelude::*; @@ -54,7 +56,7 @@ use std::{ thread, i64 }; -use super::{HttpError, LightMethod}; +use super::{HttpError, Request}; /// Refer to [`offset`]. /// @@ -96,278 +98,28 @@ lazy_static! { /// ``` /// /// [`RateLimit`]: struct.RateLimit.html - /// [`Route`]: enum.Route.html + /// [`Route`]: ../routing/enum.Route.html pub static ref ROUTES: Arc<Mutex<HashMap<Route, Arc<Mutex<RateLimit>>>>> = { Arc::new(Mutex::new(HashMap::default())) }; } -/// A representation of all routes registered within the library. These are safe -/// and memory-efficient representations of each path that functions exist for -/// in the [`http`] module. -/// -/// [`http`]: ../index.html -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Route { - /// Route for the `/channels/:channel_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsId(u64), - /// Route for the `/channels/:channel_id/invites` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdInvites(u64), - /// Route for the `/channels/:channel_id/messages` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessages(u64), - /// Route for the `/channels/:channel_id/messages/bulk-delete` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesBulkDelete(u64), - /// Route for the `/channels/:channel_id/messages/:message_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - // This route is a unique case. The ratelimit for message _deletions_ is - // different than the overall route ratelimit. - // - // Refer to the docs on [Rate Limits] in the yellow warning section. - // - // Additionally, this needs to be a `LightMethod` from the parent module - // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`. - // - // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits - ChannelsIdMessagesId(LightMethod, u64), - /// Route for the `/channels/:channel_id/messages/:message_id/ack` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdAck(u64), - /// Route for the `/channels/:channel_id/messages/:message_id/reactions` - /// path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdReactions(u64), - /// Route for the - /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me` - /// path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdMessagesIdReactionsUserIdType(u64), - /// Route for the `/channels/:channel_id/permissions/:target_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPermissionsOverwriteId(u64), - /// Route for the `/channels/:channel_id/pins` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPins(u64), - /// Route for the `/channels/:channel_id/pins/:message_id` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdPinsMessageId(u64), - /// Route for the `/channels/:channel_id/typing` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdTyping(u64), - /// Route for the `/channels/:channel_id/webhooks` path. - /// - /// The data is the relevant [`ChannelId`]. - /// - /// [`ChannelId`]: ../../model/id/struct.ChannelId.html - ChannelsIdWebhooks(u64), - /// Route for the `/gateway` path. - Gateway, - /// Route for the `/gateway/bot` path. - GatewayBot, - /// Route for the `/guilds` path. - Guilds, - /// Route for the `/guilds/:guild_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsId(u64), - /// Route for the `/guilds/:guild_id/bans` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdBans(u64), - /// Route for the `/guilds/:guild_id/audit-logs` path. - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdAuditLogs(u64), - /// Route for the `/guilds/:guild_id/bans/:user_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdBansUserId(u64), - /// Route for the `/guilds/:guild_id/channels/:channel_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdChannels(u64), - /// Route for the `/guilds/:guild_id/embed` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmbed(u64), - /// Route for the `/guilds/:guild_id/emojis` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmojis(u64), - /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdEmojisId(u64), - /// Route for the `/guilds/:guild_id/integrations` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrations(u64), - /// Route for the `/guilds/:guild_id/integrations/:integration_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrationsId(u64), - /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync` - /// path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdIntegrationsIdSync(u64), - /// Route for the `/guilds/:guild_id/invites` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdInvites(u64), - /// Route for the `/guilds/:guild_id/members` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembers(u64), - /// Route for the `/guilds/:guild_id/members/:user_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersId(u64), - /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersIdRolesId(u64), - /// Route for the `/guilds/:guild_id/members/@me/nick` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdMembersMeNick(u64), - /// Route for the `/guilds/:guild_id/prune` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdPrune(u64), - /// Route for the `/guilds/:guild_id/regions` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRegions(u64), - /// Route for the `/guilds/:guild_id/roles` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRoles(u64), - /// Route for the `/guilds/:guild_id/roles/:role_id` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdRolesId(u64), - /// Route for the `/guilds/:guild_id/vanity-url` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdVanityUrl(u64), - /// Route for the `/guilds/:guild_id/webhooks` path. - /// - /// The data is the relevant [`GuildId`]. - /// - /// [`GuildId`]: struct.GuildId.html - GuildsIdWebhooks(u64), - /// Route for the `/invites/:code` path. - InvitesCode, - /// Route for the `/users/:user_id` path. - UsersId, - /// Route for the `/users/@me` path. - UsersMe, - /// Route for the `/users/@me/channels` path. - UsersMeChannels, - /// Route for the `/users/@me/guilds` path. - UsersMeGuilds, - /// Route for the `/users/@me/guilds/:guild_id` path. - UsersMeGuildsId, - /// Route for the `/voice/regions` path. - VoiceRegions, - /// Route for the `/webhooks/:webhook_id` path. - WebhooksId(u64), - /// Route where no ratelimit headers are in place (i.e. user account-only - /// routes). - /// - /// This is a special case, in that if the route is `None` then pre- and - /// post-hooks are not executed. - None, -} - -pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> - where F: Fn() -> RequestBuilder<'a> { +pub(super) fn perform(req: Request) -> Result<Response> { loop { // This will block if another thread already has the global // unlocked already (due to receiving an x-ratelimit-global). let _ = GLOBAL.lock(); + // Destructure the tuple instead of retrieving the third value to + // take advantage of the type system. If `RouteInfo::deconstruct` + // returns a different number of tuple elements in the future, directly + // accessing a certain index (e.g. `req.route.deconstruct().1`) would + // mean this code would not indicate it might need to be updated for the + // new tuple element amount. + // + // This isn't normally important, but might be for ratelimiting. + let (_, route, _) = req.route.deconstruct(); + // Perform pre-checking here: // // - get the route's relevant rate @@ -390,7 +142,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> let mut lock = bucket.lock(); lock.pre_hook(&route); - let response = super::retry(&f)?; + let response = super::retry(&req)?; // Check if an offset has been calculated yet to determine the time // difference from Discord can the client. @@ -452,7 +204,7 @@ pub(crate) fn perform<'a, F>(route: Route, f: F) -> Result<Response> /// 429s. /// /// [`ROUTES`]: struct.ROUTES.html -/// [`Route`]: enum.Route.html +/// [`Route`]: ../routing/enum.Route.html /// [Discord docs]: https://discordapp.com/developers/docs/topics/rate-limits #[derive(Clone, Debug, Default)] pub struct RateLimit { diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 0000000..92dd073 --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,116 @@ +use constants; +use hyper::{ + client::{Body, RequestBuilder as HyperRequestBuilder}, + header::{Authorization, ContentType, Headers, UserAgent}, +}; +use super::{ + CLIENT, + TOKEN, + routing::RouteInfo, +}; + +pub struct RequestBuilder<'a> { + body: Option<&'a [u8]>, + headers: Option<Headers>, + route: RouteInfo<'a>, +} + +impl<'a> RequestBuilder<'a> { + pub fn new(route_info: RouteInfo<'a>) -> Self { + Self { + body: None, + headers: None, + route: route_info, + } + } + + pub fn build(self) -> Request<'a> { + Request::new(self) + } + + pub fn body(&mut self, body: Option<&'a [u8]>) -> &mut Self { + self.body = body; + + self + } + + pub fn headers(&mut self, headers: Option<Headers>) -> &mut Self { + self.headers = headers; + + self + } + + pub fn route(&mut self, route_info: RouteInfo<'a>) -> &mut Self { + self.route = route_info; + + self + } +} + +#[derive(Clone, Debug)] +pub struct Request<'a> { + pub(super) body: Option<&'a [u8]>, + pub(super) headers: Option<Headers>, + pub(super) route: RouteInfo<'a>, +} + +impl<'a> Request<'a> { + pub fn new(builder: RequestBuilder<'a>) -> Self { + let RequestBuilder { body, headers, route } = builder; + + Self { body, headers, route } + } + + pub fn build(&'a self) -> HyperRequestBuilder<'a> { + let Request { + body, + headers: ref request_headers, + route: ref route_info, + } = *self; + let (method, _, path) = route_info.deconstruct(); + + let mut builder = CLIENT.request( + method.hyper_method(), + &path.into_owned(), + ); + + if let Some(ref bytes) = body { + builder = builder.body(Body::BufBody(bytes, bytes.len())); + } + + let mut headers = Headers::new(); + headers.set(UserAgent(constants::USER_AGENT.to_string())); + headers.set(Authorization(TOKEN.lock().clone())); + headers.set(ContentType::json()); + + if let Some(request_headers) = request_headers.clone() { + headers.extend(request_headers.iter()); + } + + builder.headers(headers) + } + + pub fn body_ref(&self) -> &Option<&'a [u8]> { + &self.body + } + + pub fn body_mut(&mut self) -> &mut Option<&'a [u8]> { + &mut self.body + } + + pub fn headers_ref(&self) -> &Option<Headers> { + &self.headers + } + + pub fn headers_mut(&mut self) -> &mut Option<Headers> { + &mut self.headers + } + + pub fn route_ref(&self) -> &RouteInfo { + &self.route + } + + pub fn route_mut(&mut self) -> &mut RouteInfo<'a> { + &mut self.route + } +} diff --git a/src/http/routing.rs b/src/http/routing.rs new file mode 100644 index 0000000..52be61c --- /dev/null +++ b/src/http/routing.rs @@ -0,0 +1,1433 @@ +use std::{ + borrow::Cow, + fmt::{Display, Write}, +}; +use super::LightMethod; + +/// A representation of all routes registered within the library. These are safe +/// and memory-efficient representations of each path that functions exist for +/// in the [`http`] module. +/// +/// [`http`]: ../index.html +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Route { + /// Route for the `/channels/:channel_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsId(u64), + /// Route for the `/channels/:channel_id/invites` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdInvites(u64), + /// Route for the `/channels/:channel_id/messages` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessages(u64), + /// Route for the `/channels/:channel_id/messages/bulk-delete` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesBulkDelete(u64), + /// Route for the `/channels/:channel_id/messages/:message_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + // This route is a unique case. The ratelimit for message _deletions_ is + // different than the overall route ratelimit. + // + // Refer to the docs on [Rate Limits] in the yellow warning section. + // + // Additionally, this needs to be a `LightMethod` from the parent module + // and _not_ a `hyper` `Method` due to `hyper`'s not deriving `Copy`. + // + // [Rate Limits]: https://discordapp.com/developers/docs/topics/rate-limits + ChannelsIdMessagesId(LightMethod, u64), + /// Route for the `/channels/:channel_id/messages/:message_id/ack` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdAck(u64), + /// Route for the `/channels/:channel_id/messages/:message_id/reactions` + /// path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdReactions(u64), + /// Route for the + /// `/channels/:channel_id/messages/:message_id/reactions/:reaction/@me` + /// path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdMessagesIdReactionsUserIdType(u64), + /// Route for the `/channels/:channel_id/permissions/:target_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPermissionsOverwriteId(u64), + /// Route for the `/channels/:channel_id/pins` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPins(u64), + /// Route for the `/channels/:channel_id/pins/:message_id` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdPinsMessageId(u64), + /// Route for the `/channels/:channel_id/typing` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdTyping(u64), + /// Route for the `/channels/:channel_id/webhooks` path. + /// + /// The data is the relevant [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/id/struct.ChannelId.html + ChannelsIdWebhooks(u64), + /// Route for the `/gateway` path. + Gateway, + /// Route for the `/gateway/bot` path. + GatewayBot, + /// Route for the `/guilds` path. + Guilds, + /// Route for the `/guilds/:guild_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsId(u64), + /// Route for the `/guilds/:guild_id/bans` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdBans(u64), + /// Route for the `/guilds/:guild_id/audit-logs` path. + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdAuditLogs(u64), + /// Route for the `/guilds/:guild_id/bans/:user_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdBansUserId(u64), + /// Route for the `/guilds/:guild_id/channels/:channel_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdChannels(u64), + /// Route for the `/guilds/:guild_id/embed` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdEmbed(u64), + /// Route for the `/guilds/:guild_id/emojis` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdEmojis(u64), + /// Route for the `/guilds/:guild_id/emojis/:emoji_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdEmojisId(u64), + /// Route for the `/guilds/:guild_id/integrations` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdIntegrations(u64), + /// Route for the `/guilds/:guild_id/integrations/:integration_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdIntegrationsId(u64), + /// Route for the `/guilds/:guild_id/integrations/:integration_id/sync` + /// path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdIntegrationsIdSync(u64), + /// Route for the `/guilds/:guild_id/invites` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdInvites(u64), + /// Route for the `/guilds/:guild_id/members` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdMembers(u64), + /// Route for the `/guilds/:guild_id/members/:user_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdMembersId(u64), + /// Route for the `/guilds/:guild_id/members/:user_id/roles/:role_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdMembersIdRolesId(u64), + /// Route for the `/guilds/:guild_id/members/@me/nick` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdMembersMeNick(u64), + /// Route for the `/guilds/:guild_id/prune` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdPrune(u64), + /// Route for the `/guilds/:guild_id/regions` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdRegions(u64), + /// Route for the `/guilds/:guild_id/roles` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdRoles(u64), + /// Route for the `/guilds/:guild_id/roles/:role_id` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdRolesId(u64), + /// Route for the `/guilds/:guild_id/vanity-url` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdVanityUrl(u64), + /// Route for the `/guilds/:guild_id/webhooks` path. + /// + /// The data is the relevant [`GuildId`]. + /// + /// [`GuildId`]: ../../model/id/struct.GuildId.html + GuildsIdWebhooks(u64), + /// Route for the `/invites/:code` path. + InvitesCode, + /// Route for the `/users/:user_id` path. + UsersId, + /// Route for the `/users/@me` path. + UsersMe, + /// Route for the `/users/@me/channels` path. + UsersMeChannels, + /// Route for the `/users/@me/guilds` path. + UsersMeGuilds, + /// Route for the `/users/@me/guilds/:guild_id` path. + UsersMeGuildsId, + /// Route for the `/voice/regions` path. + VoiceRegions, + /// Route for the `/webhooks/:webhook_id` path. + WebhooksId(u64), + /// Route where no ratelimit headers are in place (i.e. user account-only + /// routes). + /// + /// This is a special case, in that if the route is `None` then pre- and + /// post-hooks are not executed. + None, +} + +impl Route { + pub fn channel(channel_id: u64) -> String { + format!(api!("/channels/{}"), channel_id) + } + + pub fn channel_invites(channel_id: u64) -> String { + format!(api!("/channels/{}/invites"), channel_id) + } + + pub fn channel_message(channel_id: u64, message_id: u64) -> String { + format!(api!("/channels/{}/messages/{}"), channel_id, message_id) + } + + pub fn channel_message_reaction<D, T>( + channel_id: u64, + message_id: u64, + user_id: D, + reaction_type: T + ) -> String where D: Display, T: Display { + format!( + api!("/channels/{}/messages/{}/reactions/{}/{}"), + channel_id, + message_id, + reaction_type, + user_id, + ) + } + + pub fn channel_message_reactions( + channel_id: u64, + message_id: u64, + ) -> String { + api!("/channels/{}/messages/{}/reactions", channel_id, message_id) + } + + pub fn channel_message_reactions_list( + channel_id: u64, + message_id: u64, + reaction: &str, + limit: u8, + after: Option<u64>, + ) -> String { + let mut uri = format!( + api!("/channels/{}/messages/{}/reactions/{}?limit={}"), + channel_id, + message_id, + reaction, + limit, + ); + + if let Some(after) = after { + let _ = write!(uri, "&after={}", after); + } + + uri + } + + pub fn channel_messages(channel_id: u64, query: Option<&str>) -> String { + format!( + api!("/channels/{}/messages{}"), + channel_id, + query.unwrap_or(""), + ) + } + + pub fn channel_messages_bulk_delete(channel_id: u64) -> String { + format!(api!("/channels/{}/messages/bulk-delete"), channel_id) + } + + pub fn channel_permission(channel_id: u64, target_id: u64) -> String { + format!(api!("/channels/{}/permissions/{}"), channel_id, target_id) + } + + pub fn channel_pin(channel_id: u64, message_id: u64) -> String { + format!(api!("/channels/{}/pins/{}"), channel_id, message_id) + } + + pub fn channel_pins(channel_id: u64) -> String { + format!(api!("/channels/{}/pins"), channel_id) + } + + pub fn channel_typing(channel_id: u64) -> String { + format!(api!("/channels/{}/typing"), channel_id) + } + + pub fn channel_webhooks(channel_id: u64) -> String { + format!(api!("/channels/{}/webhooks"), channel_id) + } + + pub fn gateway() -> &'static str { + api!("/gateway") + } + + pub fn gateway_bot() -> &'static str { + api!("/gateway/bot") + } + + pub fn group_recipient(group_id: u64, user_id: u64) -> String { + format!(api!("/channels/{}/recipients/{}"), group_id, user_id) + } + + pub fn guild(guild_id: u64) -> String { + format!(api!("/guilds/{}"), guild_id) + } + + pub fn guild_audit_logs( + guild_id: u64, + action_type: Option<u8>, + user_id: Option<u64>, + before: Option<u64>, + limit: Option<u8>, + ) -> String { + let mut s = format!( + api!("/guilds/{}/audit-logs?"), + guild_id, + ); + + if let Some(action_type) = action_type { + let _ = write!(s, "&action_type={}", action_type); + } + + if let Some(before) = before { + let _ = write!(s, "&before={}", before); + } + + if let Some(limit) = limit { + let _ = write!(s, "&limit={}", limit); + } + + if let Some(user_id) = user_id { + let _ = write!(s, "&user_id={}", user_id); + } + + s + } + + pub fn guild_ban(guild_id: u64, user_id: u64) -> String { + format!(api!("/guilds/{}/bans/{}"), guild_id, user_id) + } + + pub fn guild_ban_optioned( + guild_id: u64, + user_id: u64, + delete_message_days: u8, + reason: &str, + ) -> String { + format!( + api!("/guilds/{}/bans/{}?delete_message_days={}&reason={}"), + guild_id, + user_id, + delete_message_days, + reason, + ) + } + + pub fn guild_bans(guild_id: u64) -> String { + format!(api!("/guilds/{}/bans"), guild_id) + } + + pub fn guild_channels(guild_id: u64) -> String { + format!(api!("/guilds/{}/channels"), guild_id) + } + + pub fn guild_embed(guild_id: u64) -> String { + format!(api!("/guilds/{}/embed"), guild_id) + } + + pub fn guild_emojis(guild_id: u64) -> String { + format!(api!("/guilds/{}/emojis"), guild_id) + } + + pub fn guild_emoji(guild_id: u64, emoji_id: u64) -> String { + format!(api!("/guilds/{}/emojis/{}"), guild_id, emoji_id) + } + + pub fn guild_integration( + guild_id: u64, + integration_id: u64, + ) -> String { + format!(api!("/guilds/{}/integrations/{}"), guild_id, integration_id) + } + + pub fn guild_integration_sync( + guild_id: u64, + integration_id: u64, + ) -> String { + format!( + api!("/guilds/{}/integrations/{}/sync"), + guild_id, + integration_id, + ) + } + + pub fn guild_integrations(guild_id: u64) -> String { + format!(api!("/guilds/{}/integrations"), guild_id) + } + + pub fn guild_invites(guild_id: u64) -> String { + format!(api!("/guilds/{}/invites"), guild_id) + } + + pub fn guild_member(guild_id: u64, user_id: u64) -> String { + format!(api!("/guilds/{}/members/{}"), guild_id, user_id) + } + + pub fn guild_member_role( + guild_id: u64, + user_id: u64, + role_id: u64, + ) -> String { + format!( + api!("/guilds/{}/members/{}/roles/{}"), + guild_id, + user_id, + role_id, + ) + } + + pub fn guild_members(guild_id: u64) -> String { + format!(api!("/guilds/{}/members"), guild_id) + } + + pub fn guild_members_optioned( + guild_id: u64, + after: Option<u64>, + limit: Option<u64>, + ) -> String { + let mut s = format!(api!("/guilds/{}/members?"), guild_id); + + if let Some(after) = after { + let _ = write!(s, "&after={}", after); + } + + if let Some(limit) = limit { + let _ = write!(s, "&limit={}", limit); + } + + s + } + + pub fn guild_nickname(guild_id: u64) -> String { + format!(api!("/guilds/{}/members/@me/nick"), guild_id) + } + + pub fn guild_prune(guild_id: u64, days: u64) -> String { + format!(api!("/guilds/{}/prune?days={}"), guild_id, days) + } + + pub fn guild_regions(guild_id: u64) -> String { + format!(api!("/guilds/{}/regions"), guild_id) + } + + pub fn guild_role(guild_id: u64, role_id: u64) -> String { + format!(api!("/guilds/{}/roles/{}"), guild_id, role_id) + } + + pub fn guild_roles(guild_id: u64) -> String { + format!(api!("/guilds/{}/roles"), guild_id) + } + + pub fn guild_vanity_url(guild_id: u64) -> String { + format!(api!("/guilds/{}/vanity-url"), guild_id) + } + + pub fn guild_webhooks(guild_id: u64) -> String { + format!(api!("/guilds/{}/webhooks"), guild_id) + } + + pub fn guilds() -> &'static str { + api!("/guilds") + } + + pub fn invite(code: &str) -> String { + format!(api!("/invites/{}"), code) + } + + pub fn invite_optioned(code: &str, stats: bool) -> String { + format!(api!("/invites/{}?with_counts={}"), code, stats) + } + + pub fn oauth2_application_current() -> &'static str { + api!("/oauth2/applications/@me") + } + + pub fn private_channel() -> &'static str { + api!("/users/@me/channels") + } + + pub fn status_incidents_unresolved() -> &'static str { + status!("/incidents/unresolved.json") + } + + pub fn status_maintenances_active() -> &'static str { + status!("/scheduled-maintenances/active.json") + } + + pub fn status_maintenances_upcoming() -> &'static str { + status!("/scheduled-maintenances/upcoming.json") + } + + pub fn user<D: Display>(target: D) -> String { + format!(api!("/users/{}"), target) + } + + pub fn user_dm_channels<D: Display>(target: D) -> String { + format!(api!("/users/{}/channels"), target) + } + + pub fn user_guild<D: Display>(target: D, guild_id: u64) -> String { + format!(api!("/users/{}/guilds/{}"), target, guild_id) + } + + pub fn user_guilds<D: Display>(target: D) -> String { + format!(api!("/users/{}/guilds"), target) + } + + pub fn user_guilds_optioned<D: Display>( + target: D, + after: Option<u64>, + before: Option<u64>, + limit: u64, + ) -> String { + let mut s = format!(api!("/users/{}/guilds?limit={}&"), target, limit); + + if let Some(after) = after { + let _ = write!(s, "&after={}", after); + } + + if let Some(before) = before { + let _ = write!(s, "&before={}", before); + } + + s + } + + pub fn voice_regions() -> &'static str { + api!("/voice/regions") + } + + pub fn webhook(webhook_id: u64) -> String { + format!(api!("/webhooks/{}"), webhook_id) + } + + pub fn webhook_with_token<D>(webhook_id: u64, token: D) -> String + where D: Display { + format!(api!("/webhooks/{}/{}"), webhook_id, token) + } + + pub fn webhook_with_token_optioned<D>(webhook_id: u64, token: D, wait: bool) + -> String where D: Display { + format!(api!("/webhooks/{}/{}?wait={}"), webhook_id, token, wait) + } +} + +#[derive(Clone, Debug)] +pub enum RouteInfo<'a> { + AddGroupRecipient { + group_id: u64, + user_id: u64, + }, + AddMemberRole { + guild_id: u64, + role_id: u64, + user_id: u64, + }, + GuildBanUser { + guild_id: u64, + user_id: u64, + delete_message_days: Option<u8>, + reason: Option<&'a str>, + }, + BroadcastTyping { + channel_id: u64, + }, + CreateChannel { + guild_id: u64, + }, + CreateEmoji { + guild_id: u64, + }, + CreateGuild, + CreateGuildIntegration { + guild_id: u64, + integration_id: u64, + }, + CreateInvite { + channel_id: u64, + }, + CreateMessage { + channel_id: u64, + }, + CreatePermission { + channel_id: u64, + target_id: u64, + }, + CreatePrivateChannel, + CreateReaction { + channel_id: u64, + message_id: u64, + reaction: &'a str, + }, + CreateRole { + guild_id: u64, + }, + CreateWebhook { + channel_id: u64, + }, + DeleteChannel { + channel_id: u64, + }, + DeleteEmoji { + guild_id: u64, + emoji_id: u64, + }, + DeleteGuild { + guild_id: u64, + }, + DeleteGuildIntegration { + guild_id: u64, + integration_id: u64, + }, + DeleteInvite { + code: &'a str, + }, + DeleteMessage { + channel_id: u64, + message_id: u64, + }, + DeleteMessages { + channel_id: u64, + }, + DeleteMessageReactions { + channel_id: u64, + message_id: u64, + }, + DeletePermission { + channel_id: u64, + target_id: u64, + }, + DeleteReaction { + channel_id: u64, + message_id: u64, + user: &'a str, + reaction: &'a str, + }, + DeleteRole { + guild_id: u64, + role_id: u64, + }, + DeleteWebhook { + webhook_id: u64, + }, + DeleteWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + EditChannel { + channel_id: u64, + }, + EditEmoji { + guild_id: u64, + emoji_id: u64, + }, + EditGuild { + guild_id: u64, + }, + EditGuildChannels { + guild_id: u64, + }, + EditGuildEmbed { + guild_id: u64, + }, + EditMember { + guild_id: u64, + user_id: u64, + }, + EditMessage { + channel_id: u64, + message_id: u64, + }, + EditNickname { + guild_id: u64, + }, + EditProfile, + EditRole { + guild_id: u64, + role_id: u64, + }, + EditWebhook { + webhook_id: u64, + }, + EditWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + ExecuteWebhook { + token: &'a str, + wait: bool, + webhook_id: u64, + }, + GetActiveMaintenance, + GetAuditLogs { + action_type: Option<u8>, + before: Option<u64>, + guild_id: u64, + limit: Option<u8>, + user_id: Option<u64>, + }, + GetBans { + guild_id: u64, + }, + GetBotGateway, + GetChannel { + channel_id: u64, + }, + GetChannelInvites { + channel_id: u64, + }, + GetChannelWebhooks { + channel_id: u64, + }, + GetChannels { + guild_id: u64, + }, + GetCurrentApplicationInfo, + GetCurrentUser, + GetGateway, + GetGuild { + guild_id: u64, + }, + GetGuildEmbed { + guild_id: u64, + }, + GetGuildIntegrations { + guild_id: u64, + }, + GetGuildInvites { + guild_id: u64, + }, + GetGuildMembers { + after: Option<u64>, + limit: Option<u64>, + guild_id: u64, + }, + GetGuildPruneCount { + days: u64, + guild_id: u64, + }, + GetGuildRegions { + guild_id: u64, + }, + GetGuildRoles { + guild_id: u64, + }, + GetGuildVanityUrl { + guild_id: u64, + }, + GetGuildWebhooks { + guild_id: u64, + }, + GetGuilds { + after: Option<u64>, + before: Option<u64>, + limit: u64, + }, + GetInvite { + code: &'a str, + stats: bool, + }, + GetMember { + guild_id: u64, + user_id: u64, + }, + GetMessage { + channel_id: u64, + message_id: u64, + }, + GetMessages { + channel_id: u64, + query: String, + }, + GetPins { + channel_id: u64, + }, + GetReactionUsers { + after: Option<u64>, + channel_id: u64, + limit: u8, + message_id: u64, + reaction: String, + }, + GetUnresolvedIncidents, + GetUpcomingMaintenances, + GetUser { + user_id: u64, + }, + GetUserDmChannels, + GetVoiceRegions, + GetWebhook { + webhook_id: u64, + }, + GetWebhookWithToken { + token: &'a str, + webhook_id: u64, + }, + KickMember { + guild_id: u64, + user_id: u64, + }, + LeaveGroup { + group_id: u64, + }, + LeaveGuild { + guild_id: u64, + }, + RemoveGroupRecipient { + group_id: u64, + user_id: u64, + }, + PinMessage { + channel_id: u64, + message_id: u64, + }, + RemoveBan { + guild_id: u64, + user_id: u64, + }, + RemoveMemberRole { + guild_id: u64, + role_id: u64, + user_id: u64, + }, + StartGuildPrune { + days: u64, + guild_id: u64, + }, + StartIntegrationSync { + guild_id: u64, + integration_id: u64, + }, + StatusIncidentsUnresolved, + StatusMaintenancesActive, + StatusMaintenancesUpcoming, + UnpinMessage { + channel_id: u64, + message_id: u64, + }, +} + +impl<'a> RouteInfo<'a> { + pub fn deconstruct(&self) -> (LightMethod, Route, Cow<str>) { + match *self { + RouteInfo::AddGroupRecipient { group_id, user_id } => ( + LightMethod::Put, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::AddMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Put, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::GuildBanUser { + guild_id, + delete_message_days, + reason, + user_id, + } => ( + // TODO + LightMethod::Put, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban_optioned( + guild_id, + user_id, + delete_message_days.unwrap_or(0), + reason.unwrap_or(""), + )), + ), + RouteInfo::BroadcastTyping { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdTyping(channel_id), + Cow::from(Route::channel_typing(channel_id)), + ), + RouteInfo::CreateChannel { guild_id } => ( + LightMethod::Post, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::CreateEmoji { guild_id } => ( + LightMethod::Post, + Route::GuildsIdEmojis(guild_id), + Cow::from(Route::guild_emojis(guild_id)), + ), + RouteInfo::CreateGuild => ( + LightMethod::Post, + Route::Guilds, + Cow::from(Route::guilds()), + ), + RouteInfo::CreateGuildIntegration { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::CreateInvite { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::CreateMessage { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages(channel_id, None)), + ), + RouteInfo::CreatePermission { channel_id, target_id } => ( + LightMethod::Put, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::CreatePrivateChannel => ( + LightMethod::Post, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::CreateReaction { channel_id, message_id, reaction } => ( + LightMethod::Put, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + "@me", + reaction, + )), + ), + RouteInfo::CreateRole { guild_id } => ( + LightMethod::Post, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::CreateWebhook { channel_id } => ( + LightMethod::Post, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::DeleteChannel { channel_id } => ( + LightMethod::Delete, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::DeleteEmoji { emoji_id, guild_id } => ( + LightMethod::Delete, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::DeleteGuild { guild_id } => ( + LightMethod::Delete, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::DeleteGuildIntegration { guild_id, integration_id } => ( + LightMethod::Delete, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration(guild_id, integration_id)), + ), + RouteInfo::DeleteInvite { code } => ( + LightMethod::Delete, + Route::InvitesCode, + Cow::from(Route::invite(code)), + ), + RouteInfo::DeleteMessageReactions { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions( + channel_id, + message_id, + )), + ), + RouteInfo::DeleteMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesId(LightMethod::Delete, message_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::DeleteMessages { channel_id } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesBulkDelete(channel_id), + Cow::from(Route::channel_messages_bulk_delete(channel_id)), + ), + RouteInfo::DeletePermission { channel_id, target_id } => ( + LightMethod::Delete, + Route::ChannelsIdPermissionsOverwriteId(channel_id), + Cow::from(Route::channel_permission(channel_id, target_id)), + ), + RouteInfo::DeleteReaction { + channel_id, + message_id, + reaction, + user, + } => ( + LightMethod::Delete, + Route::ChannelsIdMessagesIdReactionsUserIdType(channel_id), + Cow::from(Route::channel_message_reaction( + channel_id, + message_id, + user, + reaction, + )) + ), + RouteInfo::DeleteRole { guild_id, role_id } => ( + LightMethod::Delete, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::DeleteWebhook { webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::DeleteWebhookWithToken { token, webhook_id } => ( + LightMethod::Delete, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::EditChannel { channel_id } => ( + LightMethod::Patch, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::EditEmoji { emoji_id, guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmojisId(guild_id), + Cow::from(Route::guild_emoji(guild_id, emoji_id)), + ), + RouteInfo::EditGuild { guild_id } => ( + LightMethod::Patch, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::EditGuildChannels { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::EditGuildEmbed { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::EditMember { guild_id, user_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::EditMessage { channel_id, message_id } => ( + LightMethod::Patch, + Route::ChannelsIdMessagesId(LightMethod::Patch, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::EditNickname { guild_id } => ( + LightMethod::Patch, + Route::GuildsIdMembersMeNick(guild_id), + Cow::from(Route::guild_nickname(guild_id)), + ), + RouteInfo::EditProfile => ( + LightMethod::Patch, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::EditRole { guild_id, role_id } => ( + LightMethod::Patch, + Route::GuildsIdRolesId(guild_id), + Cow::from(Route::guild_role(guild_id, role_id)), + ), + RouteInfo::EditWebhook { webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::EditWebhookWithToken { token, webhook_id } => ( + LightMethod::Patch, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::ExecuteWebhook { token, wait, webhook_id } => ( + LightMethod::Post, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token_optioned( + webhook_id, + token, + wait, + )), + ), + RouteInfo::GetActiveMaintenance => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::GetAuditLogs { + action_type, + before, + guild_id, + limit, + user_id, + } => ( + LightMethod::Get, + Route::GuildsIdAuditLogs(guild_id), + Cow::from(Route::guild_audit_logs( + guild_id, + action_type, + user_id, + before, + limit, + )), + ), + RouteInfo::GetBans { guild_id } => ( + LightMethod::Get, + Route::GuildsIdBans(guild_id), + Cow::from(Route::guild_bans(guild_id)), + ), + RouteInfo::GetBotGateway => ( + LightMethod::Get, + Route::GatewayBot, + Cow::from(Route::gateway_bot()), + ), + RouteInfo::GetChannel { channel_id } => ( + LightMethod::Get, + Route::ChannelsId(channel_id), + Cow::from(Route::channel(channel_id)), + ), + RouteInfo::GetChannelInvites { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdInvites(channel_id), + Cow::from(Route::channel_invites(channel_id)), + ), + RouteInfo::GetChannelWebhooks { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdWebhooks(channel_id), + Cow::from(Route::channel_webhooks(channel_id)), + ), + RouteInfo::GetChannels { guild_id } => ( + LightMethod::Get, + Route::GuildsIdChannels(guild_id), + Cow::from(Route::guild_channels(guild_id)), + ), + RouteInfo::GetCurrentApplicationInfo => ( + LightMethod::Get, + Route::None, + Cow::from(Route::oauth2_application_current()), + ), + RouteInfo::GetCurrentUser => ( + LightMethod::Get, + Route::UsersMe, + Cow::from(Route::user("@me")), + ), + RouteInfo::GetGateway => ( + LightMethod::Get, + Route::Gateway, + Cow::from(Route::gateway()), + ), + RouteInfo::GetGuild { guild_id } => ( + LightMethod::Get, + Route::GuildsId(guild_id), + Cow::from(Route::guild(guild_id)), + ), + RouteInfo::GetGuildEmbed { guild_id } => ( + LightMethod::Get, + Route::GuildsIdEmbed(guild_id), + Cow::from(Route::guild_embed(guild_id)), + ), + RouteInfo::GetGuildIntegrations { guild_id } => ( + LightMethod::Get, + Route::GuildsIdIntegrations(guild_id), + Cow::from(Route::guild_integrations(guild_id)), + ), + RouteInfo::GetGuildInvites { guild_id } => ( + LightMethod::Get, + Route::GuildsIdInvites(guild_id), + Cow::from(Route::guild_invites(guild_id)), + ), + RouteInfo::GetGuildMembers { after, guild_id, limit } => ( + LightMethod::Get, + Route::GuildsIdMembers(guild_id), + Cow::from(Route::guild_members_optioned(guild_id, after, limit)), + ), + RouteInfo::GetGuildPruneCount { days, guild_id } => ( + LightMethod::Get, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::GetGuildRegions { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRegions(guild_id), + Cow::from(Route::guild_regions(guild_id)), + ), + RouteInfo::GetGuildRoles { guild_id } => ( + LightMethod::Get, + Route::GuildsIdRoles(guild_id), + Cow::from(Route::guild_roles(guild_id)), + ), + RouteInfo::GetGuildVanityUrl { guild_id } => ( + LightMethod::Get, + Route::GuildsIdVanityUrl(guild_id), + Cow::from(Route::guild_vanity_url(guild_id)), + ), + RouteInfo::GetGuildWebhooks { guild_id } => ( + LightMethod::Get, + Route::GuildsIdWebhooks(guild_id), + Cow::from(Route::guild_webhooks(guild_id)), + ), + RouteInfo::GetGuilds { after, before, limit } => ( + LightMethod::Get, + Route::UsersMeGuilds, + Cow::from(Route::user_guilds_optioned( + "@me", + after, + before, + limit, + )), + ), + RouteInfo::GetInvite { code, stats } => ( + LightMethod::Get, + Route::InvitesCode, + Cow::from(Route::invite_optioned(code, stats)), + ), + RouteInfo::GetMember { guild_id, user_id } => ( + LightMethod::Get, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::GetMessage { channel_id, message_id } => ( + LightMethod::Get, + Route::ChannelsIdMessagesId(LightMethod::Get, channel_id), + Cow::from(Route::channel_message(channel_id, message_id)), + ), + RouteInfo::GetMessages { channel_id, ref query } => ( + LightMethod::Get, + Route::ChannelsIdMessages(channel_id), + Cow::from(Route::channel_messages( + channel_id, + Some(query.as_ref()), + )), + ), + RouteInfo::GetPins { channel_id } => ( + LightMethod::Get, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pins(channel_id)), + ), + RouteInfo::GetReactionUsers { + after, + channel_id, + limit, + message_id, + ref reaction, + } => ( + LightMethod::Get, + Route::ChannelsIdMessagesIdReactions(channel_id), + Cow::from(Route::channel_message_reactions_list( + channel_id, + message_id, + reaction, + limit, + after, + )), + ), + RouteInfo::GetUnresolvedIncidents => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::GetUpcomingMaintenances => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::GetUser { user_id } => ( + LightMethod::Get, + Route::UsersId, + Cow::from(Route::user(user_id)), + ), + RouteInfo::GetUserDmChannels => ( + LightMethod::Get, + Route::UsersMeChannels, + Cow::from(Route::user_dm_channels("@me")), + ), + RouteInfo::GetVoiceRegions => ( + LightMethod::Get, + Route::VoiceRegions, + Cow::from(Route::voice_regions()), + ), + RouteInfo::GetWebhook { webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook(webhook_id)), + ), + RouteInfo::GetWebhookWithToken { token, webhook_id } => ( + LightMethod::Get, + Route::WebhooksId(webhook_id), + Cow::from(Route::webhook_with_token(webhook_id, token)), + ), + RouteInfo::KickMember { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersId(guild_id), + Cow::from(Route::guild_member(guild_id, user_id)), + ), + RouteInfo::LeaveGroup { group_id } => ( + LightMethod::Delete, + Route::ChannelsId(group_id), + Cow::from(Route::channel(group_id)), + ), + RouteInfo::LeaveGuild { guild_id } => ( + LightMethod::Delete, + Route::UsersMeGuildsId, + Cow::from(Route::user_guild("@me", guild_id)), + ), + RouteInfo::RemoveGroupRecipient { group_id, user_id } => ( + LightMethod::Delete, + Route::None, + Cow::from(Route::group_recipient(group_id, user_id)), + ), + RouteInfo::PinMessage { channel_id, message_id } => ( + LightMethod::Put, + Route::ChannelsIdPins(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + RouteInfo::RemoveBan { guild_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdBansUserId(guild_id), + Cow::from(Route::guild_ban(guild_id, user_id)), + ), + RouteInfo::RemoveMemberRole { guild_id, role_id, user_id } => ( + LightMethod::Delete, + Route::GuildsIdMembersIdRolesId(guild_id), + Cow::from(Route::guild_member_role(guild_id, user_id, role_id)), + ), + RouteInfo::StartGuildPrune { days, guild_id } => ( + LightMethod::Post, + Route::GuildsIdPrune(guild_id), + Cow::from(Route::guild_prune(guild_id, days)), + ), + RouteInfo::StartIntegrationSync { guild_id, integration_id } => ( + LightMethod::Post, + Route::GuildsIdIntegrationsId(guild_id), + Cow::from(Route::guild_integration_sync( + guild_id, + integration_id, + )), + ), + RouteInfo::StatusIncidentsUnresolved => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_incidents_unresolved()), + ), + RouteInfo::StatusMaintenancesActive => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_active()), + ), + RouteInfo::StatusMaintenancesUpcoming => ( + LightMethod::Get, + Route::None, + Cow::from(Route::status_maintenances_upcoming()), + ), + RouteInfo::UnpinMessage { channel_id, message_id } => ( + LightMethod::Delete, + Route::ChannelsIdPinsMessageId(channel_id), + Cow::from(Route::channel_pin(channel_id, message_id)), + ), + } + } +} diff --git a/src/internal/macros.rs b/src/internal/macros.rs index c4d2b6f..a03e9bd 100644 --- a/src/internal/macros.rs +++ b/src/internal/macros.rs @@ -1,36 +1,6 @@ //! A set of macros for easily working with internals. -#[cfg(feature = "http")] -macro_rules! request { - ($route:expr, $method:ident($body:expr), $url:expr, $($rest:tt)*) => {{ - let client = request_client!(); - - request($route, || client - .$method(&format!(api!($url), $($rest)*)) - .body(&$body))? - }}; - ($route:expr, $method:ident($body:expr), $url:expr) => {{ - let client = request_client!(); - - request($route, || client - .$method(api!($url)) - .body(&$body))? - }}; - ($route:expr, $method:ident, $url:expr, $($rest:tt)*) => {{ - let client = request_client!(); - - request($route, || client - .$method(&format!(api!($url), $($rest)*)))? - }}; - ($route:expr, $method:ident, $url:expr) => {{ - let client = request_client!(); - - request($route, || client - .$method(api!($url)))? - }}; -} - -#[cfg(feature = "http")] +#[cfg(feature = "model")] macro_rules! request_client { () => {{ use hyper::net::HttpsConnector; @@ -89,15 +59,6 @@ macro_rules! feature_cache { } } -#[cfg(all(feature = "client", not(feature = "framework")))] -macro_rules! feature_framework { - ($enabled:block else $disabled:block) => { - { - $disabled - } - } -} - macro_rules! enum_number { ($name:ident { $($variant:ident, )* }) => { impl ::serde::Serialize for $name { @@ -32,8 +32,10 @@ //! ```rust,no_run //! #[macro_use] extern crate serenity; //! -//! use serenity::client::Client; -//! use serenity::prelude::EventHandler; +//! # #[cfg(all(feature = "client", feature = "standard_framework"))] +//! # mod inner { +//! # +//! use serenity::client::{Client, EventHandler}; //! use serenity::framework::standard::StandardFramework; //! use std::env; //! @@ -41,10 +43,11 @@ //! //! impl EventHandler for Handler {} //! -//! fn main() { +//! pub fn main() { //! // Login with a bot token from the environment //! let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("token"), Handler) //! .expect("Error creating client"); +//! //! client.with_framework(StandardFramework::new() //! .configure(|c| c.prefix("~")) // set the bot's prefix to "~" //! .cmd("ping", ping)); @@ -58,6 +61,13 @@ //! command!(ping(_context, message) { //! let _ = message.reply("Pong!"); //! }); +//! # +//! # } +//! # +//! # #[cfg(all(feature = "client", feature = "standard_framework"))] +//! # fn main() { inner::main() } +//! # #[cfg(not(all(feature = "client", feature = "standard_framework")))] +//! # fn main() {} //! ``` //! //! ### Full Examples @@ -100,10 +110,12 @@ #[macro_use] extern crate bitflags; +#[allow(unused_imports)] #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; +#[allow(unused_imports)] #[macro_use] extern crate serde_json; @@ -140,6 +152,11 @@ extern crate typemap; #[cfg(feature = "evzht9h3nznqzwl")] extern crate evzht9h3nznqzwl as websocket; +#[allow(unused_imports)] +#[cfg(test)] +#[macro_use] +extern crate matches; + #[macro_use] mod internal; @@ -211,7 +228,7 @@ lazy_static! { /// CACHE.write().settings_mut().max_messages(10); /// ``` /// - /// [`CurrentUser`]: model/struct.CurrentUser.html + /// [`CurrentUser`]: model/user/struct.CurrentUser.html /// [`Cache`]: cache/struct.Cache.html /// [cache module documentation]: cache/index.html pub static ref CACHE: RwLock<Cache> = RwLock::new(Cache::default()); diff --git a/src/model/application.rs b/src/model/application.rs index 894e8df..004f40d 100644 --- a/src/model/application.rs +++ b/src/model/application.rs @@ -20,7 +20,7 @@ pub struct ApplicationInfo { /// If a bot is public, anyone may invite it to their [`Guild`]. While a bot /// is private, only the owner may add it to a guild. /// - /// [`Guild`]: struct.Guild.html + /// [`Guild`]: ../guild/struct.Guild.html #[serde(default = "default_true")] pub bot_public: bool, /// Indicator of whether the bot requires an OAuth2 code grant. diff --git a/src/model/channel/attachment.rs b/src/model/channel/attachment.rs index 0530b6f..23d69dd 100644 --- a/src/model/channel/attachment.rs +++ b/src/model/channel/attachment.rs @@ -103,8 +103,8 @@ impl Attachment { /// Returns an [`Error::Hyper`] when there is a problem retrieving the /// attachment. /// - /// [`Error::Hyper`]: ../enum.Error.html#variant.Hyper - /// [`Error::Io`]: ../enum.Error.html#variant.Io + /// [`Error::Hyper`]: ../../enum.Error.html#variant.Hyper + /// [`Error::Io`]: ../../enum.Error.html#variant.Io /// [`Message`]: struct.Message.html pub fn download(&self) -> Result<Vec<u8>> { let hyper = request_client!(); diff --git a/src/model/channel/channel_category.rs b/src/model/channel/channel_category.rs index 7a49c7e..47b2281 100644 --- a/src/model/channel/channel_category.rs +++ b/src/model/channel/channel_category.rs @@ -49,7 +49,7 @@ impl ChannelCategory { /// /// **Note**: Requires the [Manage Channel] permission. /// - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { self.id.delete_permission(permission_type) diff --git a/src/model/channel/channel_id.rs b/src/model/channel/channel_id.rs index 48003ef..6715562 100644 --- a/src/model/channel/channel_id.rs +++ b/src/model/channel/channel_id.rs @@ -40,7 +40,7 @@ impl ChannelId { /// let _successful = ChannelId(7).broadcast_typing(); /// ``` /// - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[inline] pub fn broadcast_typing(&self) -> Result<()> { http::broadcast_typing(self.0) } @@ -52,11 +52,11 @@ impl ChannelId { /// /// Requires the [Manage Channels] permission. /// - /// [`GuildChannel::create_permission`]: struct.GuildChannel.html#method.create_permission - /// [`Member`]: struct.Member.html - /// [`PermissionOverwrite`]: struct.PermissionOverwrite.html - /// [`Role`]: struct.Role.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [`GuildChannel::create_permission`]: ../channel/struct.GuildChannel.html#method.create_permission + /// [`Member`]: ../guild/struct.Member.html + /// [`PermissionOverwrite`]: ../channel/struct.PermissionOverwrite.html + /// [`Role`]: ../guild/struct.Role.html + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS pub fn create_permission(&self, target: &PermissionOverwrite) -> Result<()> { let (id, kind) = match target.kind { PermissionOverwriteType::Member(id) => (id.0, "member"), @@ -81,10 +81,10 @@ impl ChannelId { /// Requires the [Add Reactions] permission, _if_ the current user is the /// first user to perform a react with a certain emoji. /// - /// [`Emoji`]: struct.Emoji.html - /// [`Message`]: struct.Message.html - /// [`Message::react`]: struct.Message.html#method.react - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [`Message`]: ../channel/struct.Message.html + /// [`Message::react`]: ../channel/struct.Message.html#method.react + /// [Add Reactions]: ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS #[inline] pub fn create_reaction<M, R>(&self, message_id: M, reaction_type: R) -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { @@ -110,9 +110,9 @@ impl ChannelId { /// Requires the [Manage Messages] permission, if the current user is not /// the author of the message. /// - /// [`Message`]: struct.Message.html - /// [`Message::delete`]: struct.Message.html#method.delete - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`Message`]: ../channel/struct.Message.html + /// [`Message::delete`]: ../channel/struct.Message.html#method.delete + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_message<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self._delete_message(message_id.into()) @@ -137,9 +137,9 @@ impl ChannelId { /// Returns [`ModelError::BulkDeleteAmount`] if an attempt was made to /// delete either 0 or more than 100 messages. /// - /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`Channel::delete_messages`]: ../channel/enum.Channel.html#method.delete_messages + /// [`ModelError::BulkDeleteAmount`]: ../error/enum.Error.html#variant.BulkDeleteAmount + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> Result<()> { let ids = message_ids .into_iter() @@ -167,7 +167,7 @@ impl ChannelId { /// /// **Note**: Requires the [Manage Channel] permission. /// - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { http::delete_permission( self.0, @@ -183,8 +183,8 @@ impl ChannelId { /// **Note**: Requires the [Manage Messages] permission, _if_ the current /// user did not perform the reaction. /// - /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`Reaction`]: ../channel/struct.Reaction.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_reaction<M, R>(&self, message_id: M, @@ -230,8 +230,8 @@ impl ChannelId { /// channel_id.edit(|c| c.name("test").bitrate(64000)); /// ``` /// - /// [`Channel`]: enum.Channel.html - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [`Channel`]: ../channel/enum.Channel.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[cfg(feature = "utils")] #[inline] pub fn edit<F: FnOnce(EditChannel) -> EditChannel>(&self, f: F) -> Result<GuildChannel> { @@ -255,10 +255,10 @@ impl ChannelId { /// is over the [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html + /// [`Message`]: ../channel/struct.Message.html + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content #[cfg(feature = "utils")] #[inline] pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> @@ -303,7 +303,7 @@ impl ChannelId { /// Gets all of the channel's invites. /// /// Requires the [Manage Channels] permission. - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn invites(&self) -> Result<Vec<RichInvite>> { http::get_channel_invites(self.0) } @@ -311,7 +311,7 @@ impl ChannelId { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { self._message(message_id.into()) @@ -331,8 +331,8 @@ impl ChannelId { /// /// Requires the [Read Message History] permission. /// - /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`Channel::messages`]: ../channel/enum.Channel.html#method.messages + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> where F: FnOnce(GetMessages) -> GetMessages { let mut map = f(GetMessages::default()).0; @@ -387,7 +387,7 @@ impl ChannelId { /// Pins a [`Message`] to the channel. /// - /// [`Message`]: struct.Message.html + /// [`Message`]: ../channel/struct.Message.html #[inline] pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self._pin(message_id.into()) @@ -399,7 +399,7 @@ impl ChannelId { /// Gets the list of [`Message`]s which are pinned to the channel. /// - /// [`Message`]: struct.Message.html + /// [`Message`]: ../channel/struct.Message.html #[inline] pub fn pins(&self) -> Result<Vec<Message>> { http::get_pins(self.0) } @@ -410,11 +410,11 @@ impl ChannelId { /// /// **Note**: Requires the [Read Message History] permission. /// - /// [`Channel::reaction_users`]: enum.Channel.html#method.reaction_users - /// [`Emoji`]: struct.Emoji.html - /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`Channel::reaction_users`]: ../channel/enum.Channel.html#method.reaction_users + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [`Message`]: ../channel/struct.Message.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY pub fn reaction_users<M, R, U>(&self, message_id: M, reaction_type: R, @@ -458,7 +458,7 @@ impl ChannelId { /// over the limit. /// /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong #[inline] pub fn say<D: ::std::fmt::Display>(&self, content: D) -> Result<Message> { self.send_message(|m| m.content(content)) @@ -514,12 +514,12 @@ impl ChannelId { /// [`HttpError::InvalidRequest(PayloadTooLarge)`][`HttpError::InvalidRequest`] /// if the file is too large to send. /// - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [`HttpError::InvalidRequest`]: ../http/enum.HttpError.html#variant.InvalidRequest - /// [`CreateMessage::content`]: ../utils/builder/struct.CreateMessage.html#method.content + /// [`ClientError::MessageTooLong`]: ../../client/enum.ClientError.html#variant.MessageTooLong + /// [`HttpError::InvalidRequest`]: ../../http/enum.HttpError.html#variant.InvalidRequest + /// [`CreateMessage::content`]: ../../builder/struct.CreateMessage.html#method.content /// [`GuildChannel`]: struct.GuildChannel.html - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "utils")] pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { @@ -556,10 +556,10 @@ impl ChannelId { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`Channel`]: enum.Channel.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`Channel`]: ../channel/enum.Channel.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`CreateMessage`]: ../../builder/struct.CreateMessage.html + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "utils")] pub fn send_message<F>(&self, f: F) -> Result<Message> where F: FnOnce(CreateMessage) -> CreateMessage { @@ -584,8 +584,8 @@ impl ChannelId { /// /// Requires the [Manage Messages] permission. /// - /// [`Message`]: struct.Message.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`Message`]: ../channel/struct.Message.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn unpin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self._unpin(message_id.into()) @@ -599,7 +599,7 @@ impl ChannelId { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn webhooks(&self) -> Result<Vec<Webhook>> { http::get_channel_webhooks(self.0) } } diff --git a/src/model/channel/embed.rs b/src/model/channel/embed.rs index 0e6fe8e..6dcccb9 100644 --- a/src/model/channel/embed.rs +++ b/src/model/channel/embed.rs @@ -75,7 +75,7 @@ impl Embed { /// /// This should only be useful in conjunction with [`Webhook::execute`]. /// - /// [`Webhook::execute`]: struct.Webhook.html + /// [`Webhook::execute`]: ../webhook/struct.Webhook.html /// /// # Examples /// diff --git a/src/model/channel/group.rs b/src/model/channel/group.rs index 4baa654..ad16fa0 100644 --- a/src/model/channel/group.rs +++ b/src/model/channel/group.rs @@ -19,8 +19,8 @@ use std::fmt::Write as FmtWrite; /// A group channel - potentially including other [`User`]s - separate from a /// [`Guild`]. /// -/// [`Guild`]: struct.Guild.html -/// [`User`]: struct.User.html +/// [`Guild`]: ../guild/struct.Guild.html +/// [`User`]: ../user/struct.User.html #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Group { /// The Id of the group channel. @@ -52,7 +52,7 @@ impl Group { /// **Note**: Groups have a limit of 10 recipients, including the current /// user. /// - /// [`http::add_group_recipient`]: ../http/fn.add_group_recipient.html + /// [`http::add_group_recipient`]: ../../http/fn.add_group_recipient.html #[inline] pub fn add_recipient<U: Into<UserId>>(&self, user: U) -> Result<()> { self._add_recipient(user.into()) @@ -79,10 +79,10 @@ impl Group { /// Requires the [Add Reactions] permission, _if_ the current user is the /// first user to perform a react with a certain emoji. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html /// [`Message::react`]: struct.Message.html#method.react - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + /// [Add Reactions]: ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS #[inline] pub fn create_reaction<M, R>(&self, message_id: M, reaction_type: R) -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { @@ -104,8 +104,8 @@ impl Group { /// delete either 0 or more than 100 messages. /// /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::BulkDeleteAmount`]: ../error/enum.Error.html#variant.BulkDeleteAmount + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> Result<()> { self.channel_id.delete_messages(message_ids) @@ -116,7 +116,7 @@ impl Group { /// /// **Note**: Requires the [Manage Channel] permission. /// - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { self.channel_id.delete_permission(permission_type) @@ -128,7 +128,7 @@ impl Group { /// user did not perform the reaction. /// /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_reaction<M, R>(&self, message_id: M, @@ -155,10 +155,10 @@ impl Group { /// is over the [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content #[inline] pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { @@ -191,7 +191,7 @@ impl Group { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { self.channel_id.message(message_id) @@ -201,7 +201,7 @@ impl Group { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> where F: FnOnce(GetMessages) -> GetMessages { @@ -243,10 +243,10 @@ impl Group { /// **Note**: Requires the [Read Message History] permission. /// /// [`Channel::reaction_users`]: enum.Channel.html#method.reaction_users - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn reaction_users<M, R, U>( &self, @@ -286,8 +286,8 @@ impl Group { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ChannelId`]: ../model/id/struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ChannelId`]: ../id/struct.ChannelId.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong #[inline] pub fn say(&self, content: &str) -> Result<Message> { self.channel_id.say(content) } @@ -305,10 +305,10 @@ impl Group { /// [`ClientError::MessageTooLong`] will be returned, containing the number /// of unicode code points over the limit. /// - /// [`ChannelId::send_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ChannelId::send_files`]: ../id/struct.ChannelId.html#method.send_files + /// [`ClientError::MessageTooLong`]: ../../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[inline] pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { @@ -322,8 +322,8 @@ impl Group { /// /// **Note**: Requires the [Send Messages] permission. /// - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`CreateMessage`]: ../../builder/struct.CreateMessage.html + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[inline] pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { self.channel_id.send_message(f) @@ -334,7 +334,7 @@ impl Group { /// Requires the [Manage Messages] permission. /// /// [`Message`]: struct.Message.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn unpin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self.channel_id.unpin(message_id) diff --git a/src/model/channel/guild_channel.rs b/src/model/channel/guild_channel.rs index 2f54fa1..b1fec05 100644 --- a/src/model/channel/guild_channel.rs +++ b/src/model/channel/guild_channel.rs @@ -63,8 +63,8 @@ pub struct GuildChannel { pub name: String, /// Permission overwrites for [`Member`]s and for [`Role`]s. /// - /// [`Member`]: struct.Member.html - /// [`Role`]: struct.Role.html + /// [`Member`]: ../guild/struct.Member.html + /// [`Role`]: ../guild/struct.Role.html pub permission_overwrites: Vec<PermissionOverwrite>, /// The position of the channel. /// @@ -102,8 +102,8 @@ impl GuildChannel { /// Returns a [`ModelError::InvalidPermissions`] if the current user does /// not have the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES pub fn broadcast_typing(&self) -> Result<()> { self.id.broadcast_typing() } /// Creates an invite leading to the given channel. @@ -225,16 +225,17 @@ impl GuildChannel { /// ``` /// /// [`Channel`]: enum.Channel.html - /// [`Member`]: struct.Member.html + /// [`Member`]: ../guild/struct.Member.html /// [`PermissionOverwrite`]: struct.PermissionOverwrite.html /// [`PermissionOverwrite::Member`]: struct.PermissionOverwrite.html#variant.Member /// [`PermissionOverwrite::Role`]: struct.PermissionOverwrite.html#variant.Role - /// [`Role`]: struct.Role.html - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html - /// [Send TTS Messages]: permissions/constant.SEND_TTS_MESSAGES.html + /// [`Role`]: ../guild/struct.Role.html + /// [Attach Files]: + /// ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES + /// [Send TTS Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_TTS_MESSAGES #[inline] pub fn create_permission(&self, target: &PermissionOverwrite) -> Result<()> { self.id.create_permission(target) @@ -269,8 +270,8 @@ impl GuildChannel { /// delete either 0 or more than 100 messages. /// /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::BulkDeleteAmount`]: ../error/enum.Error.html#variant.BulkDeleteAmount + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> Result<()> { self.id.delete_messages(message_ids) @@ -281,7 +282,7 @@ impl GuildChannel { /// /// **Note**: Requires the [Manage Channel] permission. /// - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { self.id.delete_permission(permission_type) @@ -293,7 +294,7 @@ impl GuildChannel { /// user did not perform the reaction. /// /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_reaction<M, R>(&self, message_id: M, @@ -359,10 +360,10 @@ impl GuildChannel { /// is over the [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content #[inline] pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { @@ -379,7 +380,7 @@ impl GuildChannel { /// Gets all of the channel's invites. /// /// Requires the [Manage Channels] permission. - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn invites(&self) -> Result<Vec<RichInvite>> { self.id.invites() } @@ -403,7 +404,7 @@ impl GuildChannel { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { self.id.message(message_id) @@ -416,7 +417,7 @@ impl GuildChannel { /// Requires the [Read Message History] permission. /// /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> where F: FnOnce(GetMessages) -> GetMessages { @@ -511,14 +512,14 @@ impl GuildChannel { /// Returns a [`ModelError::GuildNotFound`] if the channel's guild could /// not be found in the [`Cache`]. /// - /// [`Cache`]: ../cache/struct.Cache.html - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html + /// [`Cache`]: ../../cache/struct.Cache.html + /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound + /// [`Guild`]: ../guild/struct.Guild.html + /// [`Member`]: ../guild/struct.Member.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`User`]: ../user/struct.User.html + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "cache")] #[inline] pub fn permissions_for<U: Into<UserId>>(&self, user_id: U) -> Result<Permissions> { @@ -532,6 +533,8 @@ impl GuildChannel { } /// Pins a [`Message`] to the channel. + /// + /// [`Message`]: struct.Message.html #[inline] pub fn pin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self.id.pin(message_id) } @@ -547,10 +550,10 @@ impl GuildChannel { /// **Note**: Requires the [Read Message History] permission. /// /// [`Channel::reaction_users`]: enum.Channel.html#method.reaction_users - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY pub fn reaction_users<M, R, U>( &self, message_id: M, @@ -571,8 +574,8 @@ impl GuildChannel { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ChannelId`]: ../id/struct.ChannelId.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong #[inline] pub fn say(&self, content: &str) -> Result<Message> { self.id.say(content) } @@ -590,10 +593,10 @@ impl GuildChannel { /// [`ClientError::MessageTooLong`] will be returned, containing the number /// of unicode code points over the limit. /// - /// [`ChannelId::send_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ChannelId::send_files`]: ../id/struct.ChannelId.html#method.send_files + /// [`ClientError::MessageTooLong`]: ../../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[inline] pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { @@ -615,10 +618,10 @@ impl GuildChannel { /// Returns a [`ModelError::InvalidPermissions`] if the current user does /// not have the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong /// [`Message`]: struct.Message.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { #[cfg(feature = "cache")] { @@ -637,7 +640,7 @@ impl GuildChannel { /// Requires the [Manage Messages] permission. /// /// [`Message`]: struct.Message.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn unpin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self.id.unpin(message_id) @@ -647,7 +650,7 @@ impl GuildChannel { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } } diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 3134725..8b580ff 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -52,7 +52,7 @@ pub struct Message { pub mention_everyone: bool, /// Array of [`Role`]s' Ids mentioned in the message. /// - /// [`Role`]: struct.Role.html + /// [`Role`]: ../guild/struct.Role.html pub mention_roles: Vec<RoleId>, /// Array of users mentioned in the message. pub mentions: Vec<User>, @@ -138,9 +138,9 @@ impl Message { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::InvalidUser`]: enum.ModelError.html#variant.InvalidUser - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`ModelError::InvalidUser`]: ../error/enum.Error.html#variant.InvalidUser + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES pub fn delete(&self) -> Result<()> { #[cfg(feature = "cache")] { @@ -166,9 +166,9 @@ impl Message { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES pub fn delete_reactions(&self) -> Result<()> { #[cfg(feature = "cache")] { @@ -210,10 +210,10 @@ impl Message { /// is over [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::InvalidUser`]: enum.ModelError.html#variant.InvalidUser - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`ModelError::InvalidUser`]: ../error/enum.Error.html#variant.InvalidUser + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content pub fn edit<F>(&mut self, f: F) -> Result<()> where F: FnOnce(EditMessage) -> EditMessage { #[cfg(feature = "cache")] @@ -312,10 +312,10 @@ impl Message { /// /// **Note**: Requires the [Read Message History] permission. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn reaction_users<R, U>( &self, @@ -402,8 +402,8 @@ impl Message { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES.html pub fn pin(&self) -> Result<()> { #[cfg(feature = "cache")] { @@ -427,10 +427,11 @@ impl Message { /// [`ModelError::InvalidPermissions`] if the current user does not have the /// required [permissions]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`Emoji`]: struct.Emoji.html - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html - /// [permissions]: permissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [Add Reactions]: + /// ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS + /// [permissions]: ../permissions/index.html #[inline] pub fn react<R: Into<ReactionType>>(&self, reaction_type: R) -> Result<()> { self._react(&reaction_type.into()) @@ -468,9 +469,9 @@ impl Message { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES pub fn reply(&self, content: &str) -> Result<Message> { if let Some(length_over) = Message::overflow_length(content) { return Err(Error::Model(ModelError::MessageTooLong(length_over))); @@ -499,7 +500,7 @@ impl Message { /// Checks whether the message mentions passed [`UserId`]. /// - /// [`UserId`]: ../../model/id/struct.UserId.html + /// [`UserId`]: ../id/struct.UserId.html #[inline] pub fn mentions_user_id<I: Into<UserId>>(&self, id: I) -> bool { self._mentions_user_id(id.into()) @@ -526,8 +527,8 @@ impl Message { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required permissions. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES pub fn unpin(&self) -> Result<()> { #[cfg(feature = "cache")] { @@ -605,6 +606,12 @@ impl Message { } } +impl AsRef<MessageId> for Message { + fn as_ref(&self) -> &MessageId { + &self.id + } +} + impl From<Message> for MessageId { /// Gets the Id of a `Message`. fn from(message: Message) -> MessageId { message.id } diff --git a/src/model/channel/mod.rs b/src/model/channel/mod.rs index 8d586a6..05a117d 100644 --- a/src/model/channel/mod.rs +++ b/src/model/channel/mod.rs @@ -41,14 +41,14 @@ pub enum Channel { Group(Arc<RwLock<Group>>), /// A [text] or [voice] channel within a [`Guild`]. /// - /// [`Guild`]: struct.Guild.html + /// [`Guild`]: ../guild/struct.Guild.html /// [text]: enum.ChannelType.html#variant.Text /// [voice]: enum.ChannelType.html#variant.Voice Guild(Arc<RwLock<GuildChannel>>), /// A private channel to another [`User`]. No other users may access the /// channel. For multi-user "private channels", use a group. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html Private(Arc<RwLock<PrivateChannel>>), /// A category of [`GuildChannel`]s /// @@ -57,15 +57,11 @@ pub enum Channel { } impl Channel { - - ///////////////////////////////////////////////////////////////////////// - // Adapter for each variant - ///////////////////////////////////////////////////////////////////////// - /// Converts from `Channel` to `Option<Arc<RwLock<Group>>>`. /// /// Converts `self` into an `Option<Arc<RwLock<Group>>>`, consuming `self`, - /// and discarding a GuildChannel, PrivateChannel, or ChannelCategory, if any. + /// and discarding a `GuildChannel`, `PrivateChannel`, or `ChannelCategory`, + /// if any. /// /// # Examples /// @@ -73,9 +69,13 @@ impl Channel { /// /// ```rust,no_run /// # extern crate serenity; + /// # /// # use self::serenity::model::id::ChannelId; + /// # + /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).get().unwrap(); + /// # let channel = ChannelId(0).get().unwrap(); + /// # /// match channel.group() { /// Some(group_lock) => { /// if let Some(ref name) = group_lock.read().name { @@ -86,10 +86,12 @@ impl Channel { /// }, /// None => { println!("It's not a group!"); }, /// } + /// # /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// fn main() {} /// ``` - - pub fn group(self) -> Option<Arc<RwLock<Group>>> { match self { Channel::Group(lock) => Some(lock), @@ -99,8 +101,9 @@ impl Channel { /// Converts from `Channel` to `Option<Arc<RwLock<GuildChannel>>>`. /// - /// Converts `self` into an `Option<Arc<RwLock<GuildChannel>>>`, consuming `self`, - /// and discarding a Group, PrivateChannel, or ChannelCategory, if any. + /// Converts `self` into an `Option<Arc<RwLock<GuildChannel>>>`, consuming + /// `self`, and discarding a `Group`, `PrivateChannel`, or + /// `ChannelCategory`, if any. /// /// # Examples /// @@ -108,18 +111,25 @@ impl Channel { /// /// ```rust,no_run /// # extern crate serenity; + /// # /// # use self::serenity::model::id::ChannelId; + /// # + /// # #[cfg(feature = "model")] /// # fn main() { - /// let channel = ChannelId(0).get().unwrap(); + /// # let channel = ChannelId(0).get().unwrap(); + /// # /// match channel.guild() { /// Some(guild_lock) => { /// println!("It's a guild named {}!", guild_lock.read().name); /// }, /// None => { println!("It's not a guild!"); }, /// } + /// # /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// fn main() {} /// ``` - pub fn guild(self) -> Option<Arc<RwLock<GuildChannel>>> { match self { Channel::Guild(lock) => Some(lock), @@ -129,8 +139,9 @@ impl Channel { /// Converts from `Channel` to `Option<Arc<RwLock<PrivateChannel>>>`. /// - /// Converts `self` into an `Option<Arc<RwLock<PrivateChannel>>>`, consuming `self`, - /// and discarding a Group, GuildChannel, or ChannelCategory, if any. + /// Converts `self` into an `Option<Arc<RwLock<PrivateChannel>>>`, consuming + /// `self`, and discarding a `Group`, `GuildChannel`, or `ChannelCategory`, + /// if any. /// /// # Examples /// @@ -138,9 +149,13 @@ impl Channel { /// /// ```rust,no_run /// # extern crate serenity; + /// # /// # use self::serenity::model::id::ChannelId; + /// # + /// # #[cfg(feature = "model")] /// # fn main() { - /// # let channel = ChannelId(0).get().unwrap(); + /// # let channel = ChannelId(0).get().unwrap(); + /// # /// match channel.private() { /// Some(private_lock) => { /// let private = private_lock.read(); @@ -150,9 +165,12 @@ impl Channel { /// }, /// None => { println!("It's not a private channel!"); }, /// } + /// # /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// fn main() {} /// ``` - pub fn private(self) -> Option<Arc<RwLock<PrivateChannel>>> { match self { Channel::Private(lock) => Some(lock), @@ -162,8 +180,9 @@ impl Channel { /// Converts from `Channel` to `Option<Arc<RwLock<ChannelCategory>>>`. /// - /// Converts `self` into an `Option<Arc<RwLock<ChannelCategory>>>`, consuming `self`, - /// and discarding a Group, GuildChannel, or PrivateChannel, if any. + /// Converts `self` into an `Option<Arc<RwLock<ChannelCategory>>>`, + /// consuming `self`, and discarding a `Group`, `GuildChannel`, or + /// `PrivateChannel`, if any. /// /// # Examples /// @@ -171,18 +190,25 @@ impl Channel { /// /// ```rust,no_run /// # extern crate serenity; + /// # /// # use self::serenity::model::id::ChannelId; + /// # + /// # #[cfg(feature = "model")] /// # fn main() { /// # let channel = ChannelId(0).get().unwrap(); + /// # /// match channel.category() { /// Some(category_lock) => { /// println!("It's a category named {}!", category_lock.read().name); /// }, /// None => { println!("It's not a category!"); }, /// } + /// # /// # } + /// # + /// # #[cfg(not(feature = "model"))] + /// fn main() {} /// ``` - pub fn category(self) -> Option<Arc<RwLock<ChannelCategory>>> { match self { Channel::Category(lock) => Some(lock), @@ -198,10 +224,10 @@ impl Channel { /// Requires the [Add Reactions] permission, _if_ the current user is the /// first user to perform a react with a certain emoji. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html /// [`Message::react`]: struct.Message.html#method.react - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + /// [Add Reactions]: ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -245,7 +271,7 @@ impl Channel { /// /// [`Message`]: struct.Message.html /// [`Message::delete`]: struct.Message.html#method.delete - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -259,7 +285,7 @@ impl Channel { /// user did not perform the reaction. /// /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -288,10 +314,10 @@ impl Channel { /// is over the [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -319,7 +345,7 @@ impl Channel { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -342,7 +368,7 @@ impl Channel { /// let _messages = channel.messages(|g| g.after(id).limit(100)); /// ``` /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -363,10 +389,10 @@ impl Channel { /// /// **Note**: Requires the [Read Message History] permission. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -404,8 +430,8 @@ impl Channel { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ChannelId`]: struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ChannelId`]: ../id/struct.ChannelId.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -425,10 +451,10 @@ impl Channel { /// [`ClientError::MessageTooLong`] will be returned, containing the number /// of unicode code points over the limit. /// - /// [`ChannelId::send_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ChannelId::send_files`]: ../id/struct.ChannelId.html#method.send_files + /// [`ClientError::MessageTooLong`]: ../../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -453,9 +479,9 @@ impl Channel { /// over the limit. /// /// [`Channel`]: enum.Channel.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`CreateMessage`]: ../../builder/struct.CreateMessage.html + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -469,7 +495,7 @@ impl Channel { /// Requires the [Manage Messages] permission. /// /// [`Message`]: struct.Message.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[cfg(feature = "model")] #[deprecated(since = "0.4.2", note = "Use the inner channel's method")] #[inline] @@ -677,3 +703,93 @@ pub enum PermissionOverwriteType { /// A role which is having its permission overwrites edited. Role(RoleId), } + +#[cfg(test)] +mod test { + #[cfg(all(feature = "model", feature = "utils"))] + mod model_utils { + use model::prelude::*; + use parking_lot::RwLock; + use std::collections::HashMap; + use std::sync::Arc; + + fn group() -> Group { + Group { + channel_id: ChannelId(1), + icon: None, + last_message_id: None, + last_pin_timestamp: None, + name: None, + owner_id: UserId(2), + recipients: HashMap::new(), + } + } + + fn guild_channel() -> GuildChannel { + GuildChannel { + id: ChannelId(1), + bitrate: None, + category_id: None, + guild_id: GuildId(2), + kind: ChannelType::Text, + last_message_id: None, + last_pin_timestamp: None, + name: "nsfw-stuff".to_string(), + permission_overwrites: vec![], + position: 0, + topic: None, + user_limit: None, + nsfw: false, + } + } + + fn private_channel() -> PrivateChannel { + PrivateChannel { + id: ChannelId(1), + last_message_id: None, + last_pin_timestamp: None, + kind: ChannelType::Private, + recipient: Arc::new(RwLock::new(User { + id: UserId(2), + avatar: None, + bot: false, + discriminator: 1, + name: "ab".to_string(), + })), + } + } + + #[test] + fn nsfw_checks() { + let mut channel = guild_channel(); + assert!(channel.is_nsfw()); + channel.kind = ChannelType::Voice; + assert!(!channel.is_nsfw()); + + channel.kind = ChannelType::Text; + channel.name = "nsfw-".to_string(); + assert!(!channel.is_nsfw()); + + channel.name = "nsfw".to_string(); + assert!(channel.is_nsfw()); + channel.kind = ChannelType::Voice; + assert!(!channel.is_nsfw()); + channel.kind = ChannelType::Text; + + channel.name = "nsf".to_string(); + channel.nsfw = true; + assert!(channel.is_nsfw()); + channel.nsfw = false; + assert!(!channel.is_nsfw()); + + let channel = Channel::Guild(Arc::new(RwLock::new(channel))); + assert!(!channel.is_nsfw()); + + let group = group(); + assert!(!group.is_nsfw()); + + let private_channel = private_channel(); + assert!(!private_channel.is_nsfw()); + } + } +} diff --git a/src/model/channel/private_channel.rs b/src/model/channel/private_channel.rs index 91a019c..2657d3b 100644 --- a/src/model/channel/private_channel.rs +++ b/src/model/channel/private_channel.rs @@ -58,10 +58,10 @@ impl PrivateChannel { /// Requires the [Add Reactions] permission, _if_ the current user is the /// first user to perform a react with a certain emoji. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html /// [`Message::react`]: struct.Message.html#method.react - /// [Add Reactions]: permissions/constant.ADD_REACTIONS.html + /// [Add Reactions]: ../permissions/struct.Permissions.html#associatedconstant.ADD_REACTIONS pub fn create_reaction<M, R>(&self, message_id: M, reaction_type: R) -> Result<()> where M: Into<MessageId>, R: Into<ReactionType> { self.id.create_reaction(message_id, reaction_type) @@ -88,8 +88,8 @@ impl PrivateChannel { /// delete either 0 or more than 100 messages. /// /// [`Channel::delete_messages`]: enum.Channel.html#method.delete_messages - /// [`ModelError::BulkDeleteAmount`]: ../enum.ModelError.html#variant.BulkDeleteAmount - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [`ModelError::BulkDeleteAmount`]: ../error/enum.Error.html#variant.BulkDeleteAmount + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_messages<T: AsRef<MessageId>, It: IntoIterator<Item=T>>(&self, message_ids: It) -> Result<()> { self.id.delete_messages(message_ids) @@ -100,7 +100,7 @@ impl PrivateChannel { /// /// **Note**: Requires the [Manage Channel] permission. /// - /// [Manage Channel]: permissions/constant.MANAGE_CHANNELS.html + /// [Manage Channel]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn delete_permission(&self, permission_type: PermissionOverwriteType) -> Result<()> { self.id.delete_permission(permission_type) @@ -112,7 +112,7 @@ impl PrivateChannel { /// user did not perform the reaction. /// /// [`Reaction`]: struct.Reaction.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn delete_reaction<M, R>(&self, message_id: M, @@ -138,10 +138,10 @@ impl PrivateChannel { /// is over the [`the limit`], containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`EditMessage`]: ../builder/struct.EditMessage.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`EditMessage`]: ../../builder/struct.EditMessage.html /// [`Message`]: struct.Message.html - /// [`the limit`]: ../builder/struct.EditMessage.html#method.content + /// [`the limit`]: ../../builder/struct.EditMessage.html#method.content #[inline] pub fn edit_message<F, M>(&self, message_id: M, f: F) -> Result<Message> where F: FnOnce(EditMessage) -> EditMessage, M: Into<MessageId> { @@ -163,7 +163,7 @@ impl PrivateChannel { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn message<M: Into<MessageId>>(&self, message_id: M) -> Result<Message> { self.id.message(message_id) @@ -176,7 +176,7 @@ impl PrivateChannel { /// Requires the [Read Message History] permission. /// /// [`Channel::messages`]: enum.Channel.html#method.messages - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn messages<F>(&self, f: F) -> Result<Vec<Message>> where F: FnOnce(GetMessages) -> GetMessages { @@ -194,10 +194,10 @@ impl PrivateChannel { /// **Note**: Requires the [Read Message History] permission. /// /// [`Channel::reaction_users`]: enum.Channel.html#method.reaction_users - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn reaction_users<M, R, U>(&self, message_id: M, @@ -229,8 +229,8 @@ impl PrivateChannel { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ChannelId`]: ../model/id/struct.ChannelId.html - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong + /// [`ChannelId`]: ../id/struct.ChannelId.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong #[inline] pub fn say<D: ::std::fmt::Display>(&self, content: D) -> Result<Message> { self.id.say(content) } @@ -248,10 +248,10 @@ impl PrivateChannel { /// [`ClientError::MessageTooLong`] will be returned, containing the number /// of unicode code points over the limit. /// - /// [`ChannelId::send_files`]: struct.ChannelId.html#method.send_files - /// [`ClientError::MessageTooLong`]: ../client/enum.ClientError.html#variant.MessageTooLong - /// [Attach Files]: permissions/constant.ATTACH_FILES.html - /// [Send Messages]: permissions/constant.SEND_MESSAGES.html + /// [`ChannelId::send_files`]: ../id/struct.ChannelId.html#method.send_files + /// [`ClientError::MessageTooLong`]: ../../client/enum.ClientError.html#variant.MessageTooLong + /// [Attach Files]: ../permissions/struct.Permissions.html#associatedconstant.ATTACH_FILES + /// [Send Messages]: ../permissions/struct.Permissions.html#associatedconstant.SEND_MESSAGES #[inline] pub fn send_files<'a, F, T, It: IntoIterator<Item=T>>(&self, files: It, f: F) -> Result<Message> where F: FnOnce(CreateMessage) -> CreateMessage, T: Into<AttachmentType<'a>> { @@ -269,8 +269,8 @@ impl PrivateChannel { /// is over the above limit, containing the number of unicode code points /// over the limit. /// - /// [`ModelError::MessageTooLong`]: enum.ModelError.html#variant.MessageTooLong - /// [`CreateMessage`]: ../builder/struct.CreateMessage.html + /// [`ModelError::MessageTooLong`]: ../error/enum.Error.html#variant.MessageTooLong + /// [`CreateMessage`]: ../../builder/struct.CreateMessage.html /// [`Message`]: struct.Message.html #[inline] pub fn send_message<F: FnOnce(CreateMessage) -> CreateMessage>(&self, f: F) -> Result<Message> { @@ -282,7 +282,7 @@ impl PrivateChannel { /// Requires the [Manage Messages] permission. /// /// [`Message`]: struct.Message.html - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES #[inline] pub fn unpin<M: Into<MessageId>>(&self, message_id: M) -> Result<()> { self.id.unpin(message_id) diff --git a/src/model/channel/reaction.rs b/src/model/channel/reaction.rs index 0f65e8b..94b3c32 100644 --- a/src/model/channel/reaction.rs +++ b/src/model/channel/reaction.rs @@ -34,7 +34,7 @@ pub struct Reaction { pub message_id: MessageId, /// The Id of the [`User`] that sent the reaction. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html pub user_id: UserId, } @@ -48,7 +48,7 @@ impl Reaction { /// /// Requires the [Read Message History] permission. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY #[inline] pub fn channel(&self) -> Result<Channel> { self.channel_id.get() @@ -66,9 +66,9 @@ impl Reaction { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required [permissions]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Messages]: permissions/constant.MANAGE_MESSAGES.html - /// [permissions]: permissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Messages]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_MESSAGES + /// [permissions]: ../permissions/index.html pub fn delete(&self) -> Result<()> { let user_id = feature_cache! { { @@ -109,7 +109,7 @@ impl Reaction { /// your own message cache or otherwise having the message available if /// possible. /// - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY /// [`Message`]: struct.Message.html #[inline] pub fn message(&self) -> Result<Message> { @@ -145,12 +145,12 @@ impl Reaction { /// Returns a [`ModelError::InvalidPermissions`] if the current user does /// not have the required [permissions]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`Emoji`]: struct.Emoji.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`Emoji`]: ../guild/struct.Emoji.html /// [`Message`]: struct.Message.html - /// [`User`]: struct.User.html - /// [Read Message History]: permissions/constant.READ_MESSAGE_HISTORY.html - /// [permissions]: permissions + /// [`User`]: ../user/struct.User.html + /// [Read Message History]: ../permissions/struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY + /// [permissions]: ../permissions/index.html #[inline] pub fn users<R, U>(&self, reaction_type: R, @@ -185,14 +185,14 @@ pub enum ReactionType { /// A reaction with a [`Guild`]s custom [`Emoji`], which is unique to the /// guild. /// - /// [`Emoji`]: struct.Emoji.html - /// [`Guild`]: struct.Guild.html + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [`Guild`]: ../guild/struct.Guild.html Custom { /// Whether the emoji is animated. animated: bool, /// The Id of the custom [`Emoji`]. /// - /// [`Emoji`]: struct.Emoji.html + /// [`Emoji`]: ../guild/struct.Emoji.html id: EmojiId, /// The name of the custom emoji. This is primarily used for decoration /// and distinguishing the emoji client-side. @@ -431,7 +431,7 @@ impl Display for ReactionType { /// displayed. Otherwise, if the type is a /// [unicode][`ReactionType::Unicode`], then the inner unicode is displayed. /// - /// [`Emoji::fmt`]: struct.Emoji.html#method.fmt + /// [`Emoji::fmt`]: ../guild/struct.Emoji.html#method.fmt /// [`ReactionType::Custom`]: enum.ReactionType.html#variant.Custom /// [`ReactionType::Unicode`]: enum.ReactionType.html#variant.Unicode fn fmt(&self, f: &mut Formatter) -> FmtResult { diff --git a/src/model/error.rs b/src/model/error.rs index 09aaded..05070a7 100644 --- a/src/model/error.rs +++ b/src/model/error.rs @@ -70,10 +70,10 @@ use super::Permissions; /// # fn main() { } /// ``` /// -/// [`Error`]: ../enum.Error.html -/// [`Error::Model`]: ../enum.Error.html#variant.Model -/// [`GuildId::ban`]: struct.GuildId.html#method.ban -/// [`model`]: ./index.html +/// [`Error`]: ../../enum.Error.html +/// [`Error::Model`]: ../../enum.Error.html#variant.Model +/// [`GuildId::ban`]: ../id/struct.GuildId.html#method.ban +/// [`model`]: ../index.html #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum Error { /// When attempting to delete below or above the minimum and maximum allowed @@ -88,9 +88,9 @@ pub enum Error { /// An indication that a [guild][`Guild`] could not be found by /// [Id][`GuildId`] in the [`Cache`]. /// - /// [`Guild`]: ../model/guild/struct.Guild.html - /// [`GuildId`]: ../model/id/struct.GuildId.html - /// [`Cache`]: ../cache/struct.Cache.html + /// [`Guild`]: ../guild/struct.Guild.html + /// [`GuildId`]: ../id/struct.GuildId.html + /// [`Cache`]: ../../cache/struct.Cache.html GuildNotFound, /// Indicates that there are hierarchy problems restricting an action. /// @@ -106,23 +106,23 @@ pub enum Error { /// The provided [`Permission`]s is the set of required permissions /// required. /// - /// [`Permission`]: ../model/permissions/struct.Permissions.html + /// [`Permission`]: ../permissions/struct.Permissions.html InvalidPermissions(Permissions), /// An indicator that the [current user] can not perform an action. /// - /// [current user]: ../model/user/struct.CurrentUser.html + /// [current user]: ../user/struct.CurrentUser.html InvalidUser, /// An indicator that an item is missing from the [`Cache`], and the action /// can not be continued. /// - /// [`Cache`]: ../cache/struct.Cache.html + /// [`Cache`]: ../../cache/struct.Cache.html ItemMissing, /// Indicates that a [`Message`]s content was too long and will not /// successfully send, as the length is over 2000 codepoints, or 4000 bytes. /// /// The number of bytes larger than the limit is provided. /// - /// [`Message`]: ../model/channel/struct.Message.html + /// [`Message`]: ../channel/struct.Message.html MessageTooLong(u64), /// Indicates that the current user is attempting to Direct Message another /// bot user, which is disallowed by the API. diff --git a/src/model/event.rs b/src/model/event.rs index ff4cceb..03905cf 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -34,7 +34,7 @@ use std::mem; /// [`Channel`]: ../channel/enum.Channel.html /// [`Group`]: ../channel/struct.Group.html /// [`Guild`]: ../guild/struct.Guild.html -/// [`PrivateChannel`]: ../struct.PrivateChannel.html +/// [`PrivateChannel`]: ../channel/struct.PrivateChannel.html #[derive(Clone, Debug)] pub struct ChannelCreateEvent { /// The channel that was created. @@ -148,8 +148,7 @@ impl CacheUpdate for ChannelDeleteEvent { cache.categories.remove(&channel_id); }, - // We ignore these two due to the fact that the delete event for dms/groups - // will _not_ fire anymore. + // We ignore these because the delete event does not fire for these. Channel::Private(_) | Channel::Group(_) => unreachable!(), }; @@ -1350,16 +1349,14 @@ pub enum Event { /// Fires the [`Client::channel_delete`] event. /// /// [`Channel`]: ../channel/enum.Channel.html - /// [`Client::channel_delete`]: - /// ../../client/struct.Client.html#channel_delete + /// [`Client::channel_delete`]: ../../client/struct.Client.html#channel_delete ChannelDelete(ChannelDeleteEvent), /// The pins for a [`Channel`] have been updated. /// /// Fires the [`Client::channel_pins_update`] event. /// /// [`Channel`]: ../channel/enum.Channel.html - /// [`Client::channel_pins_update`]: - /// ../../client/struct.Client.html#channel_pins_update + /// [`Client::channel_pins_update`]: ../../client/struct.Client.html#channel_pins_update ChannelPinsUpdate(ChannelPinsUpdateEvent), /// A [`User`] has been added to a [`Group`]. /// @@ -1422,8 +1419,7 @@ pub enum Event { /// /// Fires the [`reaction_remove`] event handler. /// - /// [`reaction_remove`]: - /// ../../prelude/trait.EventHandler.html#method.reaction_remove + /// [`reaction_remove`]: ../../prelude/trait.EventHandler.html#method.reaction_remove ReactionRemove(ReactionRemoveEvent), /// A request was issued to remove all [`Reaction`]s from a [`Message`]. /// diff --git a/src/model/gateway.rs b/src/model/gateway.rs index b73d900..28e3d68 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -131,6 +131,66 @@ impl Game { } } +impl<'a> From<&'a str> for Game { + fn from(name: &'a str) -> Self { + Game { + kind: GameType::Playing, + name: name.to_owned(), + url: None, + } + } +} + +impl From<String> for Game { + fn from(name: String) -> Self { + Game { + kind: GameType::Playing, + url: None, + name, + } + } +} + +impl<'a> From<(String, GameType)> for Game { + fn from((name, kind): (String, GameType)) -> Self { + Self { + url: None, + kind, + name, + } + } +} + +impl<'a> From<(&'a str, &'a str)> for Game { + fn from((name, url): (&'a str, &'a str)) -> Self { + Self { + kind: GameType::Streaming, + name: name.to_owned(), + url: Some(url.to_owned()), + } + } +} + +impl From<(String, String)> for Game { + fn from((name, url): (String, String)) -> Self { + Self { + kind: GameType::Streaming, + url: Some(url), + name, + } + } +} + +impl From<(String, GameType, String)> for Game { + fn from((name, kind, url): (String, GameType, String)) -> Self { + Self { + url: Some(url), + kind, + name, + } + } +} + impl<'de> Deserialize<'de> for Game { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> { let mut map = JsonMap::deserialize(deserializer)?; @@ -201,12 +261,12 @@ pub struct Gateway { /// Information detailing the current online status of a [`User`]. /// -/// [`User`]: struct.User.html +/// [`User`]: ../user/struct.User.html #[derive(Clone, Debug)] pub struct Presence { /// The game that a [`User`] is current playing. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html pub game: Option<Game>, /// The date of the last presence update. pub last_modified: Option<u64>, @@ -214,7 +274,7 @@ pub struct Presence { pub nick: Option<String>, /// The user's online status. pub status: OnlineStatus, - /// The Id of the [`User`]. Can be used to calculate the user's creation + /// The Id of the [`User`](../user/struct.User.html). Can be used to calculate the user's creation /// date. pub user_id: UserId, /// The associated user instance. diff --git a/src/model/guild/emoji.rs b/src/model/guild/emoji.rs index e787e46..51c660f 100644 --- a/src/model/guild/emoji.rs +++ b/src/model/guild/emoji.rs @@ -52,7 +52,8 @@ impl Emoji { /// /// **Note**: Only user accounts may use this method. /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: + /// ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS /// /// # Examples /// @@ -91,7 +92,7 @@ impl Emoji { /// /// **Note**: Only user accounts may use this method. /// - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS /// /// # Examples /// diff --git a/src/model/guild/guild_id.rs b/src/model/guild/guild_id.rs index 9c3120a..4b2bd21 100644 --- a/src/model/guild/guild_id.rs +++ b/src/model/guild/guild_id.rs @@ -43,11 +43,10 @@ impl GuildId { /// Returns a [`ModelError::DeleteMessageDaysAmount`] if the number of /// days' worth of messages to delete is over the maximum. /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.html#variant.DeleteMessageDaysAmount - /// [`Guild::ban`]: struct.Guild.html#method.ban - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::DeleteMessageDaysAmount`]: ../error/enum.Error.html#variant.DeleteMessageDaysAmount + /// [`Guild::ban`]: ../guild/struct.Guild.html#method.ban + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn ban<U, BO>(&self, user: U, ban_options: &BO) -> Result<()> where U: Into<UserId>, BO: BanOptions { @@ -72,7 +71,7 @@ impl GuildId { /// /// Requires the [Ban Members] permission. /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn bans(&self) -> Result<Vec<Ban>> { http::get_bans(self.0) } @@ -87,7 +86,7 @@ impl GuildId { /// Gets all of the guild's channels over the REST API. /// - /// [`Guild`]: struct.Guild.html + /// [`Guild`]: ../guild/struct.Guild.html pub fn channels(&self) -> Result<HashMap<ChannelId, GuildChannel>> { let mut channels = HashMap::new(); @@ -114,9 +113,9 @@ impl GuildId { /// let _channel = GuildId(7).create_channel("test", ChannelType::Voice, None); /// ``` /// - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`http::create_channel`]: ../http/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [`GuildChannel`]: ../channel/struct.GuildChannel.html + /// [`http::create_channel`]: ../../http/fn.create_channel.html + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> where C: Into<Option<ChannelId>> { @@ -151,10 +150,10 @@ impl GuildId { /// how to read an image from the filesystem and encode it as base64. Most /// of the example can be applied similarly for this method. /// - /// [`EditProfile::avatar`]: ../builder/struct.EditProfile.html#method.avatar - /// [`Guild::create_emoji`]: struct.Guild.html#method.create_emoji - /// [`utils::read_image`]: ../utils/fn.read_image.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [`EditProfile::avatar`]: ../../builder/struct.EditProfile.html#method.avatar + /// [`Guild::create_emoji`]: ../guild/struct.Guild.html#method.create_emoji + /// [`utils::read_image`]: ../../utils/fn.read_image.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { let map = json!({ @@ -169,7 +168,7 @@ impl GuildId { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn create_integration<I>(&self, integration_id: I, kind: &str) -> Result<()> where I: Into<IntegrationId> { @@ -195,8 +194,8 @@ impl GuildId { /// /// **Note**: Requires the [Manage Roles] permission. /// - /// [`Guild::create_role`]: struct.Guild.html#method.create_role - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [`Guild::create_role`]: ../guild/struct.Guild.html#method.create_role + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { let map = utils::vecmap_to_json_map(f(EditRole::default()).0); @@ -217,7 +216,7 @@ impl GuildId { /// /// **Note**: Requires the current user to be the owner of the guild. /// - /// [`Guild::delete`]: struct.Guild.html#method.delete + /// [`Guild::delete`]: ../guild/struct.Guild.html#method.delete #[inline] pub fn delete(&self) -> Result<PartialGuild> { http::delete_guild(self.0) } @@ -225,8 +224,8 @@ impl GuildId { /// /// Requires the [Manage Emojis] permission. /// - /// [`Emoji`]: struct.Emoji.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn delete_emoji<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<()> { self._delete_emoji(emoji_id.into()) @@ -240,7 +239,7 @@ impl GuildId { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn delete_integration<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self._delete_integration(integration_id.into()) @@ -257,9 +256,9 @@ impl GuildId { /// /// Requires the [Manage Roles] permission. /// - /// [`Role`]: struct.Role.html - /// [`Role::delete`]: struct.Role.html#method.delete - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [`Role`]: ../guild/struct.Role.html + /// [`Role::delete`]: ../guild/struct.Role.html#method.delete + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn delete_role<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { self._delete_role(role_id.into()) @@ -276,8 +275,8 @@ impl GuildId { /// **Note**: Requires the current user to have the [Manage Guild] /// permission. /// - /// [`Guild::edit`]: struct.Guild.html#method.edit - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [`Guild::edit`]: ../guild/struct.Guild.html#method.edit + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn edit<F: FnOnce(EditGuild) -> EditGuild>(&mut self, f: F) -> Result<PartialGuild> { let map = utils::vecmap_to_json_map(f(EditGuild::default()).0); @@ -292,9 +291,9 @@ impl GuildId { /// /// Requires the [Manage Emojis] permission. /// - /// [`Emoji`]: struct.Emoji.html - /// [`Emoji::edit`]: struct.Emoji.html#method.edit - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [`Emoji`]: ../guild/struct.Emoji.html + /// [`Emoji::edit`]: ../guild/struct.Emoji.html#method.edit + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn edit_emoji<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { self._edit_emoji(emoji_id.into(), name) @@ -340,7 +339,7 @@ impl GuildId { /// /// Requires the [Change Nickname] permission. /// - /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + /// [Change Nickname]: ../permissions/struct.Permissions.html#associatedconstant.CHANGE_NICKNAME #[inline] pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { http::edit_nickname(self.0, new_nickname) @@ -360,8 +359,8 @@ impl GuildId { /// GuildId(7).edit_role(RoleId(8), |r| r.hoist(true)); /// ``` /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [`Role`]: ../guild/struct.Role.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn edit_role<F, R>(&self, role_id: R, f: F) -> Result<Role> where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { @@ -387,8 +386,8 @@ impl GuildId { /// GuildId(7).edit_role_position(RoleId(8), 2); /// ``` /// - /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [`Role`]: ../guild/struct.Role.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn edit_role_position<R>(&self, role_id: R, position: u64) -> Result<Vec<Role>> where R: Into<RoleId> { @@ -425,7 +424,7 @@ impl GuildId { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/struct.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn invites(&self) -> Result<Vec<RichInvite>> { http::get_guild_invites(self.0) } @@ -433,8 +432,8 @@ impl GuildId { /// /// Requires the [Kick Members] permission. /// - /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [`Member`]: ../guild/struct.Member.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS #[inline] pub fn kick<U: Into<UserId>>(&self, user_id: U) -> Result<()> { http::kick_member(self.0, user_id.into().0) @@ -444,16 +443,26 @@ impl GuildId { #[inline] pub fn leave(&self) -> Result<()> { http::leave_guild(self.0) } - /// Gets a user's [`Member`] for the guild by Id. + /// Gets a user's [`Member`] for the guild by Id. + /// + /// If the cache feature is enabled the cache will be checked + /// first. If not found it will resort to an http request. /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html + /// [`Guild`]: ../guild/struct.Guild.html + /// [`Member`]: ../guild/struct.Member.html #[inline] pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self._member(user_id.into()) } fn _member(&self, user_id: UserId) -> Result<Member> { + #[cfg(feature = "cache")] + { + if let Some(member) = CACHE.read().member(self.0, user_id) { + return Ok(member); + } + } + http::get_member(self.0, user_id.0) } @@ -463,7 +472,7 @@ impl GuildId { /// value is 1000. Optionally pass in `after` to offset the results by a /// [`User`]'s Id. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html #[inline] pub fn members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> where U: Into<UserId> { @@ -478,7 +487,7 @@ impl GuildId { /// /// Requires the [Move Members] permission. /// - /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + /// [Move Members]: ../permissions/struct.Permissions.html#associatedconstant.MOVE_MEMBERS #[inline] pub fn move_member<C, U>(&self, user_id: U, channel_id: C) -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { @@ -504,8 +513,8 @@ impl GuildId { /// /// Requires the [Kick Members] permission. /// - /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [`Member`]: ../guild/struct.Member.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS pub fn prune_count(&self, days: u16) -> Result<GuildPrune> { let map = json!({ "days": days, @@ -546,7 +555,7 @@ impl GuildId { /// retrieve the total number of shards in use. If you already have the /// total, consider using [`utils::shard_id`]. /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html + /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] pub fn shard_id(&self) -> u64 { ::utils::shard_id(self.0, CACHE.read().shard_count) } @@ -580,7 +589,7 @@ impl GuildId { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn start_integration_sync<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self._start_integration_sync(integration_id.into()) @@ -599,9 +608,9 @@ impl GuildId { /// /// **Note**: Requires the [Kick Members] permission. /// - /// [`GuildPrune`]: struct.GuildPrune.html - /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [`GuildPrune`]: ../guild/struct.GuildPrune.html + /// [`Member`]: ../guild/struct.Member.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS #[inline] pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { let map = json!({ @@ -615,8 +624,8 @@ impl GuildId { /// /// Requires the [Ban Members] permission. /// - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { self._unban(user_id.into()) @@ -630,7 +639,7 @@ impl GuildId { /// /// **Note**: Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn vanity_url(&self) -> Result<String> { http::get_guild_vanity_url(self.0) @@ -640,7 +649,7 @@ impl GuildId { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn webhooks(&self) -> Result<Vec<Webhook>> { http::get_guild_webhooks(self.0) } } diff --git a/src/model/guild/member.rs b/src/model/guild/member.rs index 8101700..0b31ba5 100644 --- a/src/model/guild/member.rs +++ b/src/model/guild/member.rs @@ -67,7 +67,7 @@ pub struct Member { /// /// Can't be longer than 32 characters. pub nick: Option<String>, - /// Vector of Ids of [`Role`]s given to the member. + /// Vector of Ids of [`Role`](struct.Role.html)s given to the member. pub roles: Vec<RoleId>, /// Attached User struct. #[serde(deserialize_with = "deserialize_sync_user", @@ -83,7 +83,7 @@ impl Member { /// **Note**: Requires the [Manage Roles] permission. /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] #[inline] pub fn add_role<R: Into<RoleId>>(&mut self, role_id: R) -> Result<()> { @@ -112,7 +112,7 @@ impl Member { /// **Note**: Requires the [Manage Roles] permission. /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] pub fn add_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { self.roles.extend_from_slice(role_ids); @@ -140,9 +140,8 @@ impl Member { /// Returns a [`ModelError::GuildNotFound`] if the guild could not be /// found. /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[cfg(feature = "cache")] #[inline] pub fn ban<BO: BanOptions>(&self, ban_options: &BO) -> Result<()> { @@ -235,8 +234,8 @@ impl Member { /// See [`EditMember`] for the permission(s) required for separate builder /// methods, as well as usage of this. /// - /// [`Guild::edit_member`]: ../model/guild/struct.Guild.html#method.edit_member - /// [`EditMember`]: ../builder/struct.EditMember.html + /// [`Guild::edit_member`]: struct.Guild.html#method.edit_member + /// [`EditMember`]: ../../builder/struct.EditMember.html #[cfg(feature = "cache")] pub fn edit<F: FnOnce(EditMember) -> EditMember>(&self, f: F) -> Result<()> { let map = utils::vecmap_to_json_map(f(EditMember::default()).0); @@ -312,9 +311,9 @@ impl Member { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform the kick. /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS pub fn kick(&self) -> Result<()> { #[cfg(feature = "cache")] { @@ -353,8 +352,8 @@ impl Member { /// And/or returns [`ModelError::ItemMissing`] if the "default channel" of the guild is not /// found. /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound - /// [`ModelError::ItemMissing`]: enum.ModelError.html#variant.ItemMissing + /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound + /// [`ModelError::ItemMissing`]: ../error/enum.Error.html#variant.ItemMissing #[cfg(feature = "cache")] pub fn permissions(&self) -> Result<Permissions> { let guild = match self.guild_id.find() { @@ -373,7 +372,7 @@ impl Member { /// **Note**: Requires the [Manage Roles] permission. /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] #[inline] pub fn remove_role<R: Into<RoleId>>(&mut self, role_id: R) -> Result<()> { @@ -401,7 +400,7 @@ impl Member { /// **Note**: Requires the [Manage Roles] permission. /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] pub fn remove_roles(&mut self, role_ids: &[RoleId]) -> Result<()> { self.roles.retain(|r| !role_ids.contains(r)); @@ -447,9 +446,9 @@ impl Member { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[cfg(feature = "cache")] pub fn unban(&self) -> Result<()> { http::remove_ban(self.guild_id.0, self.user.read().id.0) diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs index 67b0f0e..b5c6967 100644 --- a/src/model/guild/mod.rs +++ b/src/model/guild/mod.rs @@ -101,7 +101,7 @@ pub struct Guild { /// if the [`member_count`] is greater than the `LARGE_THRESHOLD` set by /// the library. /// - /// [`ReadyEvent`]: events/struct.ReadyEvent.html + /// [`ReadyEvent`]: ../event/struct.ReadyEvent.html /// [`member_count`]: #structfield.member_count #[serde(serialize_with = "serialize_gen_map")] pub members: HashMap<UserId, Member>, @@ -109,17 +109,17 @@ pub struct Guild { /// [`Role`]s or [`User`]s with moderation permissions. /// /// [`Role`]: struct.Role.html - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html pub mfa_level: MfaLevel, /// The name of the guild. pub name: String, /// The Id of the [`User`] who owns the guild. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html pub owner_id: UserId, /// A mapping of [`User`]s' Ids to their current presences. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html #[serde(serialize_with = "serialize_gen_map")] pub presences: HashMap<UserId, Presence>, /// The region that the voice servers that the guild uses are located in. @@ -129,10 +129,8 @@ pub struct Guild { pub roles: HashMap<RoleId, Role>, /// An identifying hash of the guild's splash icon. /// - /// If the [`InviteSplash`] feature is enabled, this can be used to generate + /// If the [`"InviteSplash"`] feature is enabled, this can be used to generate /// a URL to a splash image. - /// - /// [`InviteSplash`]: enum.Feature.html#variant.InviteSplash pub splash: Option<String>, /// The ID of the channel to which system messages are sent. pub system_channel_id: Option<ChannelId>, @@ -140,7 +138,7 @@ pub struct Guild { pub verification_level: VerificationLevel, /// A mapping of of [`User`]s to their current voice state. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html #[serde(serialize_with = "serialize_gen_map")] pub voice_states: HashMap<UserId, VoiceState>, } @@ -224,12 +222,11 @@ impl Guild { /// Returns a [`ModelError::DeleteMessageDaysAmount`] if the number of /// days' worth of messages to delete is over the maximum. /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.html#variant.DeleteMessageDaysAmount - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`Guild::ban`]: struct.Guild.html#method.ban - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::DeleteMessageDaysAmount`]: ../error/enum.Error.html#variant.DeleteMessageDaysAmount + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`Guild::ban`]: ../guild/struct.Guild.html#method.ban + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn ban<U: Into<UserId>, BO: BanOptions>(&self, user: U, options: &BO) -> Result<()> { self._ban(user.into(), options) @@ -260,8 +257,8 @@ impl Guild { /// if the current user does not have permission to perform bans. /// /// [`Ban`]: struct.Ban.html - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn bans(&self) -> Result<Vec<Ban>> { #[cfg(feature = "cache")] { @@ -314,7 +311,7 @@ impl Guild { /// /// [`Guild`]: struct.Guild.html /// [`PartialGuild`]: struct.PartialGuild.html - /// [`Shard`]: ../gateway/struct.Shard.html + /// [`Shard`]: ../../gateway/struct.Shard.html /// [US West region]: enum.Region.html#variant.UsWest /// [whitelist]: https://discordapp.com/developers/docs/resources/guild#create-guild pub fn create(name: &str, region: Region, icon: Option<&str>) -> Result<PartialGuild> { @@ -346,9 +343,9 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`Channel`]: struct.Channel.html - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [`Channel`]: ../channel/enum.Channel.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> where C: Into<Option<ChannelId>> { #[cfg(feature = "cache")] @@ -379,9 +376,9 @@ impl Guild { /// how to read an image from the filesystem and encode it as base64. Most /// of the example can be applied similarly for this method. /// - /// [`EditProfile::avatar`]: ../builder/struct.EditProfile.html#method.avatar - /// [`utils::read_image`]: ../fn.read_image.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [`EditProfile::avatar`]: ../../builder/struct.EditProfile.html#method.avatar + /// [`utils::read_image`]: ../../utils/fn.read_image.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { self.id.create_emoji(name, image) @@ -391,7 +388,7 @@ impl Guild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn create_integration<I>(&self, integration_id: I, kind: &str) -> Result<()> where I: Into<IntegrationId> { @@ -417,9 +414,9 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES pub fn create_role<F>(&self, f: F) -> Result<Role> where F: FnOnce(EditRole) -> EditRole { #[cfg(feature = "cache")] @@ -444,7 +441,7 @@ impl Guild { /// If the `cache` is enabled, then returns a [`ModelError::InvalidUser`] /// if the current user is not the guild owner. /// - /// [`ModelError::InvalidUser`]: enum.ModelError.html#variant.InvalidUser + /// [`ModelError::InvalidUser`]: ../error/enum.Error.html#variant.InvalidUser pub fn delete(&self) -> Result<PartialGuild> { #[cfg(feature = "cache")] { @@ -463,7 +460,7 @@ impl Guild { /// Requires the [Manage Emojis] permission. /// /// [`Emoji`]: struct.Emoji.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn delete_emoji<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<()> { self.id.delete_emoji(emoji_id) @@ -473,7 +470,7 @@ impl Guild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn delete_integration<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self.id.delete_integration(integration_id) @@ -488,7 +485,7 @@ impl Guild { /// /// [`Role`]: struct.Role.html /// [`Role::delete`]: struct.Role.html#method.delete - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn delete_role<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { self.id.delete_role(role_id) @@ -520,8 +517,8 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD pub fn edit<F>(&mut self, f: F) -> Result<()> where F: FnOnce(EditGuild) -> EditGuild { #[cfg(feature = "cache")] @@ -564,7 +561,7 @@ impl Guild { /// /// [`Emoji`]: struct.Emoji.html /// [`Emoji::edit`]: struct.Emoji.html#method.edit - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn edit_emoji<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { self.id.edit_emoji(emoji_id, name) @@ -601,8 +598,8 @@ impl Guild { /// if the current user does not have permission to change their own /// nickname. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Change Nickname]: ../permissions/struct.Permissions.html#associatedconstant.CHANGE_NICKNAME pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { #[cfg(feature = "cache")] { @@ -628,7 +625,7 @@ impl Guild { /// guild.edit_role(RoleId(7), |r| r.hoist(true)); /// ``` /// - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn edit_role<F, R>(&self, role_id: R, f: F) -> Result<Role> where F: FnOnce(EditRole) -> EditRole, R: Into<RoleId> { @@ -648,7 +645,7 @@ impl Guild { /// ``` /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn edit_role_position<R>(&self, role_id: R, position: u64) -> Result<Vec<Role>> where R: Into<RoleId> { @@ -755,8 +752,8 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD pub fn invites(&self) -> Result<Vec<RichInvite>> { #[cfg(feature = "cache")] { @@ -780,7 +777,7 @@ impl Guild { /// Requires the [Kick Members] permission. /// /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS #[inline] pub fn kick<U: Into<UserId>>(&self, user_id: U) -> Result<()> { self.id.kick(user_id) } @@ -790,7 +787,7 @@ impl Guild { /// Gets a user's [`Member`] for the guild by Id. /// - /// [`Guild`]: struct.Guild.html + /// [`Guild`]: ../guild/struct.Guild.html /// [`Member`]: struct.Member.html #[inline] pub fn member<U: Into<UserId>>(&self, user_id: U) -> Result<Member> { self.id.member(user_id) } @@ -801,7 +798,7 @@ impl Guild { /// value is 1000. Optionally pass in `after` to offset the results by a /// [`User`]'s Id. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html #[inline] pub fn members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> where U: Into<UserId> { @@ -1174,7 +1171,7 @@ impl Guild { /// /// Requires the [Move Members] permission. /// - /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + /// [Move Members]: ../permissions/struct.Permissions.html#associatedconstant.MOVE_MEMBERS #[inline] pub fn move_member<C, U>(&self, user_id: U, channel_id: C) -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { @@ -1194,7 +1191,7 @@ impl Guild { /// Calculate a [`User`]'s permissions in a given channel in the guild. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html #[inline] pub fn permissions_in<C, U>(&self, channel_id: C, user_id: U) -> Permissions where C: Into<ChannelId>, U: Into<UserId> { @@ -1349,10 +1346,10 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`GuildPrune`]: struct.GuildPrune.html /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS pub fn prune_count(&self, days: u16) -> Result<GuildPrune> { #[cfg(feature = "cache")] { @@ -1385,7 +1382,7 @@ impl Guild { /// retrieve the total number of shards in use. If you already have the /// total, consider using [`utils::shard_id`]. /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html + /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] pub fn shard_id(&self) -> u64 { self.id.shard_id() } @@ -1425,7 +1422,7 @@ impl Guild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn start_integration_sync<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self.id.start_integration_sync(integration_id) @@ -1442,10 +1439,10 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`GuildPrune`]: struct.GuildPrune.html /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS pub fn start_prune(&self, days: u16) -> Result<GuildPrune> { #[cfg(feature = "cache")] { @@ -1468,9 +1465,9 @@ impl Guild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { #[cfg(feature = "cache")] { @@ -1488,7 +1485,7 @@ impl Guild { /// /// **Note**: Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn vanity_url(&self) -> Result<String> { self.id.vanity_url() @@ -1498,7 +1495,7 @@ impl Guild { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } @@ -2015,3 +2012,109 @@ impl VerificationLevel { } } } + +#[cfg(test)] +mod test { + #[cfg(feature = "model")] + mod model { + use chrono::prelude::*; + use model::prelude::*; + use std::collections::*; + use std::sync::Arc; + + fn gen_user() -> User { + User { + id: UserId(210), + avatar: Some("abc".to_string()), + bot: true, + discriminator: 1432, + name: "test".to_string(), + } + } + + fn gen_member() -> Member { + let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600) + .ymd(2016, 11, 08) + .and_hms(0, 0, 0); + let vec1 = Vec::new(); + let u = Arc::new(RwLock::new(gen_user())); + + Member { + deaf: false, + guild_id: GuildId(1), + joined_at: Some(dt), + mute: false, + nick: Some("aaaa".to_string()), + roles: vec1, + user: u, + } + } + + fn gen() -> Guild { + let u = gen_user(); + let m = gen_member(); + + let hm1 = HashMap::new(); + let hm2 = HashMap::new(); + let vec1 = Vec::new(); + let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600) + .ymd(2016, 11, 08) + .and_hms(0, 0, 0); + let mut hm3 = HashMap::new(); + let hm4 = HashMap::new(); + let hm5 = HashMap::new(); + let hm6 = HashMap::new(); + + hm3.insert(u.id, m); + + let notifications = DefaultMessageNotificationLevel::All; + + Guild { + afk_channel_id: Some(ChannelId(0)), + afk_timeout: 0, + channels: hm1, + default_message_notifications: notifications, + emojis: hm2, + features: vec1, + icon: Some("/avatars/210/a_aaa.webp?size=1024".to_string()), + id: GuildId(1), + joined_at: dt, + large: false, + member_count: 1, + members: hm3, + mfa_level: MfaLevel::Elevated, + name: "Spaghetti".to_string(), + owner_id: UserId(210), + presences: hm4, + region: "NA".to_string(), + roles: hm5, + splash: Some("asdf".to_string()), + verification_level: VerificationLevel::None, + voice_states: hm6, + application_id: Some(ApplicationId(0)), + explicit_content_filter: ExplicitContentFilter::None, + system_channel_id: Some(ChannelId(0)), + } + } + + + #[test] + fn member_named_username() { + let guild = gen(); + let lhs = guild + .member_named("test#1432") + .unwrap() + .display_name(); + + assert_eq!(lhs, gen_member().display_name()); + } + + #[test] + fn member_named_nickname() { + let guild = gen(); + let lhs = guild.member_named("aaaa").unwrap().display_name(); + + assert_eq!(lhs, gen_member().display_name()); + } + } +} diff --git a/src/model/guild/partial_guild.rs b/src/model/guild/partial_guild.rs index d324517..7fff7be 100644 --- a/src/model/guild/partial_guild.rs +++ b/src/model/guild/partial_guild.rs @@ -55,10 +55,9 @@ impl PartialGuild { /// Returns a [`ModelError::DeleteMessageDaysAmount`] if the number of /// days' worth of messages to delete is over the maximum. /// - /// [`ModelError::DeleteMessageDaysAmount`]: - /// enum.ModelError.html#variant.DeleteMessageDaysAmount - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`ModelError::DeleteMessageDaysAmount`]: ../error/enum.Error.html#variant.DeleteMessageDaysAmount + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS pub fn ban<U: Into<UserId>>(&self, user: U, delete_message_days: u8) -> Result<()> { if delete_message_days > 7 { return Err(Error::Model( @@ -73,7 +72,7 @@ impl PartialGuild { /// /// Requires the [Ban Members] permission. /// - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn bans(&self) -> Result<Vec<Ban>> { self.id.bans() } @@ -99,9 +98,9 @@ impl PartialGuild { /// guild.create_channel("test", ChannelType::Voice, None); /// ``` /// - /// [`GuildChannel`]: struct.GuildChannel.html - /// [`http::create_channel`]: ../http/fn.create_channel.html - /// [Manage Channels]: permissions/constant.MANAGE_CHANNELS.html + /// [`GuildChannel`]: ../channel/struct.GuildChannel.html + /// [`http::create_channel`]: ../../http/fn.create_channel.html + /// [Manage Channels]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_CHANNELS #[inline] pub fn create_channel<C>(&self, name: &str, kind: ChannelType, category: C) -> Result<GuildChannel> where C: Into<Option<ChannelId>> { @@ -121,10 +120,10 @@ impl PartialGuild { /// how to read an image from the filesystem and encode it as base64. Most /// of the example can be applied similarly for this method. /// - /// [`EditProfile::avatar`]: ../builder/struct.EditProfile.html#method.avatar + /// [`EditProfile::avatar`]: ../../builder/struct.EditProfile.html#method.avatar /// [`Guild::create_emoji`]: struct.Guild.html#method.create_emoji - /// [`utils::read_image`]: ../utils/fn.read_image.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [`utils::read_image`]: ../../utils/fn.read_image.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn create_emoji(&self, name: &str, image: &str) -> Result<Emoji> { self.id.create_emoji(name, image) @@ -134,7 +133,7 @@ impl PartialGuild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn create_integration<I>(&self, integration_id: I, kind: &str) -> Result<()> where I: Into<IntegrationId> { @@ -152,9 +151,9 @@ impl PartialGuild { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have permission to perform bans. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Guild::create_role`]: struct.Guild.html#method.create_role - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn create_role<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { self.id.create_role(f) @@ -172,7 +171,7 @@ impl PartialGuild { /// Requires the [Manage Emojis] permission. /// /// [`Emoji`]: struct.Emoji.html - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn delete_emoji<E: Into<EmojiId>>(&self, emoji_id: E) -> Result<()> { self.id.delete_emoji(emoji_id) @@ -182,7 +181,7 @@ impl PartialGuild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn delete_integration<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self.id.delete_integration(integration_id) @@ -197,7 +196,7 @@ impl PartialGuild { /// /// [`Role`]: struct.Role.html /// [`Role::delete`]: struct.Role.html#method.delete - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[inline] pub fn delete_role<R: Into<RoleId>>(&self, role_id: R) -> Result<()> { self.id.delete_role(role_id) @@ -208,7 +207,7 @@ impl PartialGuild { /// **Note**: Requires the current user to have the [Manage Guild] /// permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD pub fn edit<F>(&mut self, f: F) -> Result<()> where F: FnOnce(EditGuild) -> EditGuild { match self.id.edit(f) { @@ -242,7 +241,8 @@ impl PartialGuild { /// /// [`Emoji`]: struct.Emoji.html /// [`Emoji::edit`]: struct.Emoji.html#method.edit - /// [Manage Emojis]: permissions/constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: + /// ../permissions/struct.Permissions.html#associatedconstant.MANAGE_EMOJIS #[inline] pub fn edit_emoji<E: Into<EmojiId>>(&self, emoji_id: E, name: &str) -> Result<Emoji> { self.id.edit_emoji(emoji_id, name) @@ -281,8 +281,8 @@ impl PartialGuild { /// if the current user does not have permission to change their own /// nickname. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Change Nickname]: permissions/constant.CHANGE_NICKNAME.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Change Nickname]: ../permissions/struct.Permissions.html#associatedconstant.CHANGE_NICKNAME #[inline] pub fn edit_nickname(&self, new_nickname: Option<&str>) -> Result<()> { self.id.edit_nickname(new_nickname) @@ -299,7 +299,7 @@ impl PartialGuild { /// Requires the [Kick Members] permission. /// /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS #[inline] pub fn kick<U: Into<UserId>>(&self, user_id: U) -> Result<()> { self.id.kick(user_id) } @@ -320,7 +320,7 @@ impl PartialGuild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn invites(&self) -> Result<Vec<RichInvite>> { self.id.invites() } @@ -340,7 +340,7 @@ impl PartialGuild { /// value is 1000. Optionally pass in `after` to offset the results by a /// [`User`]'s Id. /// - /// [`User`]: struct.User.html + /// [`User`]: ../user/struct.User.html pub fn members<U>(&self, limit: Option<u64>, after: Option<U>) -> Result<Vec<Member>> where U: Into<UserId> { self.id.members(limit, after) @@ -350,7 +350,7 @@ impl PartialGuild { /// /// Requires the [Move Members] permission. /// - /// [Move Members]: permissions/constant.MOVE_MEMBERS.html + /// [Move Members]: ../permissions/struct.Permissions.html#associatedconstant.MOVE_MEMBERS #[inline] pub fn move_member<C, U>(&self, user_id: U, channel_id: C) -> Result<()> where C: Into<ChannelId>, U: Into<UserId> { @@ -363,7 +363,7 @@ impl PartialGuild { /// Requires the [Kick Members] permission. /// /// [`Member`]: struct.Member.html - /// [Kick Members]: permissions/constant.KICK_MEMBERS.html + /// [Kick Members]: ../permissions/struct.Permissions.html#associatedconstant.KICK_MEMBERS #[inline] pub fn prune_count(&self, days: u16) -> Result<GuildPrune> { self.id.prune_count(days) } @@ -376,7 +376,7 @@ impl PartialGuild { /// retrieve the total number of shards in use. If you already have the /// total, consider using [`utils::shard_id`]. /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html + /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] pub fn shard_id(&self) -> u64 { self.id.shard_id() } @@ -416,7 +416,7 @@ impl PartialGuild { /// /// Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn start_integration_sync<I: Into<IntegrationId>>(&self, integration_id: I) -> Result<()> { self.id.start_integration_sync(integration_id) @@ -426,8 +426,8 @@ impl PartialGuild { /// /// Requires the [Ban Members] permission. /// - /// [`User`]: struct.User.html - /// [Ban Members]: permissions/constant.BAN_MEMBERS.html + /// [`User`]: ../user/struct.User.html + /// [Ban Members]: ../permissions/struct.Permissions.html#associatedconstant.BAN_MEMBERS #[inline] pub fn unban<U: Into<UserId>>(&self, user_id: U) -> Result<()> { self.id.unban(user_id) } @@ -435,7 +435,7 @@ impl PartialGuild { /// /// **Note**: Requires the [Manage Guild] permission. /// - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[inline] pub fn vanity_url(&self) -> Result<String> { self.id.vanity_url() @@ -445,7 +445,7 @@ impl PartialGuild { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn webhooks(&self) -> Result<Vec<Webhook>> { self.id.webhooks() } diff --git a/src/model/guild/role.rs b/src/model/guild/role.rs index 53ec478..b66c0a0 100644 --- a/src/model/guild/role.rs +++ b/src/model/guild/role.rs @@ -50,7 +50,7 @@ pub struct Role { /// /// See the [`permissions`] module for more information. /// - /// [`permissions`]: permissions/index.html + /// [`permissions`]: ../permissions/index.html pub permissions: Permissions, /// The role's position in the position list. Roles are considered higher in /// hierarchy if their position is higher. @@ -65,7 +65,7 @@ impl Role { /// /// **Note** Requires the [Manage Roles] permission. /// - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(feature = "cache")] #[inline] pub fn delete(&self) -> Result<()> { http::delete_role(self.find_guild()?.0, self.id.0) } @@ -87,7 +87,7 @@ impl Role { /// ``` /// /// [`Role`]: struct.Role.html - /// [Manage Roles]: permissions/constant.MANAGE_ROLES.html + /// [Manage Roles]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_ROLES #[cfg(all(feature = "builder", feature = "cache"))] pub fn edit<F: FnOnce(EditRole) -> EditRole>(&self, f: F) -> Result<Role> { self.find_guild() @@ -101,7 +101,7 @@ impl Role { /// Returns a [`ModelError::GuildNotFound`] if a guild is not in the cache /// that contains the role. /// - /// [`ModelError::GuildNotFound`]: enum.ModelError.html#variant.GuildNotFound + /// [`ModelError::GuildNotFound`]: ../error/enum.Error.html#variant.GuildNotFound #[cfg(feature = "cache")] pub fn find_guild(&self) -> Result<GuildId> { for guild in CACHE.read().guilds.values() { diff --git a/src/model/id.rs b/src/model/id.rs index 046de11..5ec5c75 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -106,7 +106,7 @@ pub struct RoleId(pub u64); #[allow(derive_hash_xor_eq)] pub struct UserId(pub u64); -/// An identifier for a [`Webhook`](struct.Webhook.html). +/// An identifier for a [`Webhook`](../webhook/struct.Webhook.html). #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialOrd, Ord, Serialize)] #[allow(derive_hash_xor_eq)] pub struct WebhookId(pub u64); diff --git a/src/model/invite.rs b/src/model/invite.rs index 2bbd5d9..a6aa756 100644 --- a/src/model/invite.rs +++ b/src/model/invite.rs @@ -19,8 +19,8 @@ use {http, utils}; pub struct Invite { /// The approximate number of [`Member`]s in the related [`Guild`]. /// - /// [`Guild`]: struct.Guild.html - /// [`Member`]: struct.Member.html + /// [`Guild`]: ../guild/struct.Guild.html + /// [`Member`]: ../guild/struct.Member.html pub approximate_member_count: Option<u64>, /// The approximate number of [`Member`]s with an active session in the /// related [`Guild`]. @@ -28,14 +28,16 @@ pub struct Invite { /// An active session is defined as an open, heartbeating WebSocket connection. /// These include [invisible][`OnlineStatus::Invisible`] members. /// - /// [`OnlineStatus::Invisible`]: enum.OnlineStatus.html#variant.Invisible + /// [`OnlineStatus::Invisible`]: ../user/enum.OnlineStatus.html#variant.Invisible + /// [`Guild`]: ../guild/struct.Guild.html + /// [`Member`]: ../guild/struct.Member.html pub approximate_presence_count: Option<u64>, /// The unique code for the invite. pub code: String, /// A representation of the minimal amount of information needed about the /// [`GuildChannel`] being invited to. /// - /// [`GuildChannel`]: struct.GuildChannel.html + /// [`GuildChannel`]: ../channel/struct.GuildChannel.html pub channel: InviteChannel, /// A representation of the minimal amount of information needed about the /// [`Guild`] being invited to. @@ -63,11 +65,11 @@ impl Invite { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have the required [permission]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [`CreateInvite`]: ../builder/struct.CreateInvite.html - /// [`GuildChannel`]: struct.GuildChannel.html - /// [Create Invite]: permissions/constant.CREATE_INVITE.html - /// [permission]: permissions/index.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [`CreateInvite`]: ../../builder/struct.CreateInvite.html + /// [`GuildChannel`]: ../channel/struct.GuildChannel.html + /// [Create Invite]: ../permissions/struct.Permissions.html#associatedconstant.CREATE_INVITE + /// [permission]: ../permissions/index.html pub fn create<C, F>(channel_id: C, f: F) -> Result<RichInvite> where C: Into<ChannelId>, F: FnOnce(CreateInvite) -> CreateInvite { Self::_create(channel_id.into(), f) @@ -98,9 +100,9 @@ impl Invite { /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] /// if the current user does not have the required [permission]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - /// [permission]: permissions/index.html + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD + /// [permission]: ../permissions/index.html pub fn delete(&self) -> Result<Invite> { #[cfg(feature = "cache")] { @@ -190,7 +192,7 @@ impl InviteGuild { /// retrieve the total number of shards in use. If you already have the /// total, consider using [`utils::shard_id`]. /// - /// [`utils::shard_id`]: ../utils/fn.shard_id.html + /// [`utils::shard_id`]: ../../utils/fn.shard_id.html #[cfg(all(feature = "cache", feature = "utils"))] #[inline] pub fn shard_id(&self) -> u64 { self.id.shard_id() } @@ -226,7 +228,7 @@ impl InviteGuild { /// the [`Invite`] struct. /// /// [`Invite`]: struct.Invite.html -/// [Manage Guild]: permissions/constant.MANAGE_GUILD.html +/// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RichInvite { /// A representation of the minimal amount of information needed about the @@ -281,11 +283,11 @@ impl RichInvite { /// [`ModelError::InvalidPermissions`] if the current user does not have /// the required [permission]. /// - /// [`ModelError::InvalidPermissions`]: enum.ModelError.html#variant.InvalidPermissions + /// [`ModelError::InvalidPermissions`]: ../error/enum.Error.html#variant.InvalidPermissions /// [`Invite::delete`]: struct.Invite.html#method.delete - /// [`http::delete_invite`]: ../http/fn.delete_invite.html - /// [Manage Guild]: permissions/constant.MANAGE_GUILD.html - /// [permission]: permissions/index.html + /// [`http::delete_invite`]: ../../http/fn.delete_invite.html + /// [Manage Guild]: ../permissions/struct.Permissions.html#associatedconstant.MANAGE_GUILD.html + /// [permission]: ../permissions/index.html pub fn delete(&self) -> Result<Invite> { #[cfg(feature = "cache")] { diff --git a/src/model/misc.rs b/src/model/misc.rs index 3195c55..5c0825a 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -308,3 +308,87 @@ pub struct Maintenance { pub start: String, pub stop: String, } + +#[cfg(test)] +mod test { + use model::prelude::*; + + #[test] + fn test_formatters() { + assert_eq!(ChannelId(1).to_string(), "1"); + assert_eq!(EmojiId(2).to_string(), "2"); + assert_eq!(GuildId(3).to_string(), "3"); + assert_eq!(RoleId(4).to_string(), "4"); + assert_eq!(UserId(5).to_string(), "5"); + } + + #[cfg(feature = "utils")] + mod utils { + use model::prelude::*; + use parking_lot::RwLock; + use std::sync::Arc; + use utils::Colour; + + #[test] + fn test_mention() { + let channel = Channel::Guild(Arc::new(RwLock::new(GuildChannel { + bitrate: None, + category_id: None, + guild_id: GuildId(1), + kind: ChannelType::Text, + id: ChannelId(4), + last_message_id: None, + last_pin_timestamp: None, + name: "a".to_string(), + permission_overwrites: vec![], + position: 1, + topic: None, + user_limit: None, + nsfw: false, + }))); + let emoji = Emoji { + animated: false, + id: EmojiId(5), + name: "a".to_string(), + managed: true, + require_colons: true, + roles: vec![], + }; + let role = Role { + id: RoleId(2), + colour: Colour::ROSEWATER, + hoist: false, + managed: false, + mentionable: false, + name: "fake role".to_string(), + permissions: Permissions::empty(), + position: 1, + }; + let user = User { + id: UserId(6), + avatar: None, + bot: false, + discriminator: 4132, + name: "fake".to_string(), + }; + let member = Member { + deaf: false, + guild_id: GuildId(2), + joined_at: None, + mute: false, + nick: None, + roles: vec![], + user: Arc::new(RwLock::new(user.clone())), + }; + + assert_eq!(ChannelId(1).mention(), "<#1>"); + assert_eq!(channel.mention(), "<#4>"); + assert_eq!(emoji.mention(), "<:a:5>"); + assert_eq!(member.mention(), "<@6>"); + assert_eq!(role.mention(), "<@&2>"); + assert_eq!(role.id.mention(), "<@&2>"); + assert_eq!(user.mention(), "<@6>"); + assert_eq!(user.id.mention(), "<@6>"); + } + } +} diff --git a/src/model/permissions.rs b/src/model/permissions.rs index 0076b05..784071e 100644 --- a/src/model/permissions.rs +++ b/src/model/permissions.rs @@ -35,14 +35,14 @@ //! [`PRESET_GENERAL`]: constant.PRESET_GENERAL.html //! [`PRESET_TEXT`]: constant.PRESET_TEXT.html //! [`PRESET_VOICE`]: constant.PRESET_VOICE.html -//! [Administrator]: constant.ADMINISTRATOR.html -//! [Ban Members]: constant.BAN_MEMBERS.html -//! [Kick Members]: constant.KICK_MEMBERS.html -//! [Manage Channels]: constant.MANAGE_CHANNELS.html -//! [Manage Guild]: constant.MANAGE_GUILD.html -//! [Manage Messages]: constant.MANAGE_MESSAGES.html -//! [Manage Roles]: constant.MANAGE_ROLES.html -//! [Manage Webhooks]: constant.MANAGE_WEBHOOKS.html +//! [Administrator]: struct.Permissions.html#associatedconstant.ADMINISTRATOR +//! [Ban Members]: struct.Permissions.html#associatedconstant.BAN_MEMBERS +//! [Kick Members]: struct.Permissions.html#associatedconstant.KICK_MEMBERS +//! [Manage Channels]: struct.Permissions.html#associatedconstant.MANAGE_CHANNELS +//! [Manage Guild]: struct.Permissions.html#associatedconstant.MANAGE_GUILD +//! [Manage Messages]: struct.Permissions.html#associatedconstant.MANAGE_MESSAGES +//! [Manage Roles]: struct.Permissions.html#associatedconstant.MANAGE_ROLES +//! [Manage Webhooks]: struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; @@ -77,20 +77,20 @@ use super::utils::U64Visitor; /// permissions::general().toggle(permissions::SEND_TTS_MESSAGES); /// ``` /// -/// [Add Reactions]: constant.ADD_REACTIONS.html -/// [Attach Files]: constant.ATTACH_FILES.html -/// [Change Nickname]: constant.CHANGE_NICKNAME.html -/// [Connect]: constant.CONNECT.html -/// [Create Invite]: constant.CREATE_INVITE.html -/// [Embed Links]: constant.EMBED_LINKS.html -/// [Mention Everyone]: constant.MENTION_EVERYONE.html -/// [Read Message History]: constant.READ_MESSAGE_HISTORY.html -/// [Read Messages]: constant.READ_MESSAGES.html -/// [Send Messages]: constant.SEND_MESSAGES.html -/// [Send TTS Messages]: constant.SEND_TTS_MESSAGES.html -/// [Speak]: constant.SPEAK.html -/// [Use External Emojis]: constant.USE_EXTERNAL_EMOJIS.html -/// [Use VAD]: constant.USE_VAD.html +/// [Add Reactions]: struct.Permissions.html#associatedconstant.ADD_REACTIONS +/// [Attach Files]: struct.Permissions.html#associatedconstant.ATTACH_FILES +/// [Change Nickname]: struct.Permissions.html#associatedconstant.CHANGE_NICKNAME +/// [Connect]: struct.Permissions.html#associatedconstant.CONNECT +/// [Create Invite]: struct.Permissions.html#associatedconstant.CREATE_INVITE +/// [Embed Links]: struct.Permissions.html#associatedconstant.EMBED_LINKS +/// [Mention Everyone]: struct.Permissions.html#associatedconstant.MENTION_EVERYONE +/// [Read Message History]: struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY +/// [Read Messages]: struct.Permissions.html#associatedconstant.READ_MESSAGES +/// [Send Messages]: struct.Permissions.html#associatedconstant.SEND_MESSAGES +/// [Send TTS Messages]: struct.Permissions.html#associatedconstant.SEND_TTS_MESSAGES +/// [Speak]: struct.Permissions.html#associatedconstant.SPEAK +/// [Use External Emojis]: struct.Permissions.html#associatedconstant.USE_EXTERNAL_EMOJIS +/// [Use VAD]: struct.Permissions.html#associatedconstant.USE_VAD pub const PRESET_GENERAL: Permissions = Permissions { bits: 0b0000_0110_0011_0111_1101_1100_0100_0001, }; @@ -98,7 +98,7 @@ pub const PRESET_GENERAL: Permissions = Permissions { /// Returns a set of text-only permissions with the original `@everyone` /// permissions set to true. /// -/// This includes the text permissions given via [`general`]: +/// This includes the text permissions that are in [`PRESET_GENERAL`]: /// /// - [Add Reactions] /// - [Attach Files] @@ -112,18 +112,18 @@ pub const PRESET_GENERAL: Permissions = Permissions { /// - [Send TTS Messages] /// - [Use External Emojis] /// -/// [`general`]: fn.general.html -/// [Add Reactions]: constant.ADD_REACTIONS.html -/// [Attach Files]: constant.ATTACH_FILES.html -/// [Change Nickname]: constant.CHANGE_NICKNAME.html -/// [Create Invite]: constant.CREATE_INVITE.html -/// [Embed Links]: constant.EMBED_LINKS.html -/// [Mention Everyone]: constant.MENTION_EVERYONE.html -/// [Read Message History]: constant.READ_MESSAGE_HISTORY.html -/// [Read Messages]: constant.READ_MESSAGES.html -/// [Send Messages]: constant.SEND_MESSAGES.html -/// [Send TTS Messages]: constant.SEND_TTS_MESSAGES.html -/// [Use External Emojis]: constant.USE_EXTERNAL_EMOJIS.html +/// [`PRESET_GENERAL`]: constant.PRESET_GENERAL.html +/// [Add Reactions]: struct.Permissions.html#associatedconstant.ADD_REACTIONS +/// [Attach Files]: struct.Permissions.html#associatedconstant.ATTACH_FILES +/// [Change Nickname]: struct.Permissions.html#associatedconstant.CHANGE_NICKNAME +/// [Create Invite]: struct.Permissions.html#associatedconstant.CREATE_INVITE +/// [Embed Links]: struct.Permissions.html#associatedconstant.EMBED_LINKS +/// [Mention Everyone]: struct.Permissions.html#associatedconstant.MENTION_EVERYONE +/// [Read Message History]: struct.Permissions.html#associatedconstant.READ_MESSAGE_HISTORY +/// [Read Messages]: struct.Permissions.html#associatedconstant.READ_MESSAGES +/// [Send Messages]: struct.Permissions.html#associatedconstant.SEND_MESSAGES +/// [Send TTS Messages]: struct.Permissions.html#associatedconstant.SEND_TTS_MESSAGES +/// [Use External Emojis]: struct.Permissions.html#associatedconstant.USE_EXTERNAL_EMOJIS pub const PRESET_TEXT: Permissions = Permissions { bits: 0b0000_0000_0000_0111_1111_1100_0100_0000, }; @@ -131,16 +131,16 @@ pub const PRESET_TEXT: Permissions = Permissions { /// Returns a set of voice-only permissions with the original `@everyone` /// permissions set to true. /// -/// This includes the voice permissions given via [`general`]: +/// This includes the voice permissions that are in [`PRESET_GENERAL`]: /// /// - [Connect] /// - [Speak] /// - [Use VAD] /// -/// [`general`]: fn.general.html -/// [Connect]: constant.CONNECT.html -/// [Speak]: constant.SPEAK.html -/// [Use VAD]: constant.USE_VAD.html +/// [`PRESET_GENERAL`]: constant.PRESET_GENERAL.html +/// [Connect]: struct.Permissions.html#associatedconstant.CONNECT +/// [Speak]: struct.Permissions.html#associatedconstant.SPEAK +/// [Use VAD]: struct.Permissions.html#associatedconstant.USE_VAD pub const PRESET_VOICE: Permissions = Permissions { bits: 0b0000_0011_1111_0000_0000_0000_0000_0000, }; @@ -149,11 +149,11 @@ pub const PRESET_VOICE: Permissions = Permissions { /// [`PermissionOverwrite`]s, roles globally in a [`Guild`], and to /// [`GuildChannel`]s. /// -/// [`Guild`]: ../struct.Guild.html -/// [`GuildChannel`]: ../struct.GuildChannel.html -/// [`PermissionOverwrite`]: ../struct.PermissionOverwrite.html -/// [`Role`]: ../struct.Role.html -/// [`User`]: ../struct.User.html +/// [`Guild`]: ../guild/struct.Guild.html +/// [`GuildChannel`]: ../channel/struct.GuildChannel.html +/// [`PermissionOverwrite`]: ../channel/struct.PermissionOverwrite.html +/// [`Role`]: ../guild/struct.Role.html +/// [`User`]: ../user/struct.User.html /// #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] pub struct Permissions { @@ -164,38 +164,40 @@ __impl_bitflags! { Permissions: u64 { /// Allows for the creation of [`RichInvite`]s. /// - /// [`RichInvite`]: ../struct.RichInvite.html + /// [`RichInvite`]: ../invite/struct.RichInvite.html CREATE_INVITE = 0b0000_0000_0000_0000_0000_0000_0000_0001; /// Allows for the kicking of guild [member]s. /// - /// [member]: ../struct.Member.html + /// [member]: ../guild/struct.Member.html KICK_MEMBERS = 0b0000_0000_0000_0000_0000_0000_0000_0010; /// Allows the banning of guild [member]s. /// - /// [member]: ../struct.Member.html + /// [member]: ../guild/struct.Member.html BAN_MEMBERS = 0b0000_0000_0000_0000_0000_0000_0000_0100; /// Allows all permissions, bypassing channel [permission overwrite]s. /// - /// [permission overwrite]: ../struct.PermissionOverwrite.html + /// [permission overwrite]: ../channel/struct.PermissionOverwrite.html ADMINISTRATOR = 0b0000_0000_0000_0000_0000_0000_0000_1000; /// Allows management and editing of guild [channel]s. /// - /// [channel]: ../struct.GuildChannel.html + /// [channel]: ../channel/struct.GuildChannel.html MANAGE_CHANNELS = 0b0000_0000_0000_0000_0000_0000_0001_0000; /// Allows management and editing of the [guild]. /// - /// [guild]: ../struct.Guild.html + /// [guild]: ../guild/struct.Guild.html MANAGE_GUILD = 0b0000_0000_0000_0000_0000_0000_0010_0000; /// [`Member`]s with this permission can add new [`Reaction`]s to a /// [`Message`]. Members can still react using reactions already added /// to messages without this permission. /// - /// [`Member`]: ../struct.Member.html - /// [`Message`]: ../struct.Message.html - /// [`Reaction`]: ../struct.Reaction.html + /// [`Member`]: ../guild/struct.Member.html + /// [`Message`]: ../channel/struct.Message.html + /// [`Reaction`]: ../channel/struct.Reaction.html ADD_REACTIONS = 0b0000_0000_0000_0000_0000_0000_0100_0000; - // Allows viewing a guild's audit logs. + /// Allows viewing a guild's audit logs. VIEW_AUDIT_LOG = 0b0000_0000_0000_0000_0000_0000_1000_0000; + /// Allows the use of priority speaking in voice channels. + PRIORITY_SPEAKER = 0b0000_0000_0000_0000_0000_0001_0000_0000; /// Allows reading messages in a guild channel. If a user does not have /// this permission, then they will not be able to see the channel. READ_MESSAGES = 0b0000_0000_0000_0000_0000_0100_0000_0000; @@ -241,8 +243,8 @@ __impl_bitflags! { /// /// If this is disabled, then [`Member`]s must use push-to-talk. /// - /// [`Member`]: ../struct.Member.html - /// [voice]: ../enum.ChannelType.html#variant.Voice + /// [`Member`]: ../guild/struct.Member.html + /// [voice]: ../channel/enum.ChannelType.html#variant.Voice USE_VAD = 0b0000_0010_0000_0000_0000_0000_0000_0000; /// Allows members to change their own nickname in the guild. CHANGE_NICKNAME = 0b0000_0100_0000_0000_0000_0000_0000_0000; @@ -255,7 +257,7 @@ __impl_bitflags! { /// Allows management of emojis created without the use of an /// [`Integration`]. /// - /// [`Integration`]: ../struct.Integration.html + /// [`Integration`]: ../guild/struct.Integration.html MANAGE_EMOJIS = 0b0100_0000_0000_0000_0000_0000_0000_0000; } } @@ -265,175 +267,181 @@ impl Permissions { /// Shorthand for checking that the set of permissions contains the /// [Add Reactions] permission. /// - /// [Add Reactions]: constant.ADD_REACTIONS.html + /// [Add Reactions]: #associatedconstant.ADD_REACTIONS pub fn add_reactions(&self) -> bool { self.contains(Self::ADD_REACTIONS) } /// Shorthand for checking that the set of permissions contains the /// [Administrator] permission. /// - /// [Administrator]: constant.ADMINISTRATOR.html + /// [Administrator]: #associatedconstant.ADMINISTRATOR pub fn administrator(&self) -> bool { self.contains(Self::ADMINISTRATOR) } /// Shorthand for checking that the set of permissions contains the /// [Attach Files] permission. /// - /// [Attach Files]: constant.ATTACH_FILES.html + /// [Attach Files]: #associatedconstant.ATTACH_FILES pub fn attach_files(&self) -> bool { self.contains(Self::ATTACH_FILES) } /// Shorthand for checking that the set of permissions contains the /// [Ban Members] permission. /// - /// [Ban Members]: constant.BAN_MEMBERS.html + /// [Ban Members]: #associatedconstant.BAN_MEMBERS pub fn ban_members(&self) -> bool { self.contains(Self::BAN_MEMBERS) } /// Shorthand for checking that the set of permissions contains the /// [Change Nickname] permission. /// - /// [Change Nickname]: constant.CHANGE_NICKNAME.html + /// [Change Nickname]: #associatedconstant.CHANGE_NICKNAME pub fn change_nickname(&self) -> bool { self.contains(Self::CHANGE_NICKNAME) } /// Shorthand for checking that the set of permissions contains the /// [Connect] permission. /// - /// [Connect]: constant.CONNECT.html + /// [Connect]: #associatedconstant.CONNECT pub fn connect(&self) -> bool { self.contains(Self::CONNECT) } /// Shorthand for checking that the set of permissions contains the /// [View Audit Log] permission. /// - /// [View Audit Log]: constant.VIEW_AUDIT_LOG.html + /// [View Audit Log]: #associatedconstant.VIEW_AUDIT_LOG pub fn view_audit_log(&self) -> bool { self.contains(Self::VIEW_AUDIT_LOG) } + /// Shorthand for checking that the set of permission contains the + /// [Priority Speaker] permission. + /// + /// [Priority Speaker]: #associatedconstant.PRIORITY_SPEAKER + pub fn priority_speaker(&self) -> bool { self.contains(Self::PRIORITY_SPEAKER) } + /// Shorthand for checking that the set of permissions contains the /// [Create Invite] permission. /// - /// [Create Invite]: constant.CREATE_INVITE.html + /// [Create Invite]: #associatedconstant.CREATE_INVITE pub fn create_invite(&self) -> bool { self.contains(Self::CREATE_INVITE) } /// Shorthand for checking that the set of permissions contains the /// [Deafen Members] permission. /// - /// [Deafen Members]: constant.DEAFEN_MEMBERS.html + /// [Deafen Members]: #associatedconstant.DEAFEN_MEMBERS pub fn deafen_members(&self) -> bool { self.contains(Self::DEAFEN_MEMBERS) } /// Shorthand for checking that the set of permissions contains the /// [Embed Links] permission. /// - /// [Embed Links]: constant.EMBED_LINKS.html + /// [Embed Links]: #associatedconstant.EMBED_LINKS pub fn embed_links(&self) -> bool { self.contains(Self::EMBED_LINKS) } /// Shorthand for checking that the set of permissions contains the /// [Use External Emojis] permission. /// - /// [Use External Emojis]: constant.USE_EXTERNAL_EMOJIS.html + /// [Use External Emojis]: #associatedconstant.USE_EXTERNAL_EMOJIS pub fn external_emojis(&self) -> bool { self.contains(Self::USE_EXTERNAL_EMOJIS) } /// Shorthand for checking that the set of permissions contains the /// [Kick Members] permission. /// - /// [Kick Members]: constant.KICK_MEMBERS.html + /// [Kick Members]: #associatedconstant.KICK_MEMBERS pub fn kick_members(&self) -> bool { self.contains(Self::KICK_MEMBERS) } /// Shorthand for checking that the set of permissions contains the /// [Manage Channels] permission. /// - /// [Manage Channels]: constant.MANAGE_CHANNELS.html + /// [Manage Channels]: #associatedconstant.MANAGE_CHANNELS pub fn manage_channels(&self) -> bool { self.contains(Self::MANAGE_CHANNELS) } /// Shorthand for checking that the set of permissions contains the /// [Manage Emojis] permission. /// - /// [Manage Emojis]: constant.MANAGE_EMOJIS.html + /// [Manage Emojis]: #associatedconstant.MANAGE_EMOJIS pub fn manage_emojis(&self) -> bool { self.contains(Self::MANAGE_EMOJIS) } /// Shorthand for checking that the set of permissions contains the /// [Manage Guild] permission. /// - /// [Manage Guild]: constant.MANAGE_GUILD.html + /// [Manage Guild]: #associatedconstant.MANAGE_GUILD pub fn manage_guild(&self) -> bool { self.contains(Self::MANAGE_GUILD) } /// Shorthand for checking that the set of permissions contains the /// [Manage Messages] permission. /// - /// [Manage Messages]: constant.MANAGE_MESSAGES.html + /// [Manage Messages]: #associatedconstant.MANAGE_MESSAGES pub fn manage_messages(&self) -> bool { self.contains(Self::MANAGE_MESSAGES) } /// Shorthand for checking that the set of permissions contains the /// [Manage Nicknames] permission. /// - /// [Manage Nicknames]: constant.MANAGE_NICKNAMES.html + /// [Manage Nicknames]: #associatedconstant.MANAGE_NICKNAMES pub fn manage_nicknames(&self) -> bool { self.contains(Self::MANAGE_NICKNAMES) } /// Shorthand for checking that the set of permissions contains the /// [Manage Roles] permission. /// - /// [Manage Roles]: constant.MANAGE_ROLES.html + /// [Manage Roles]: #associatedconstant.MANAGE_ROLES pub fn manage_roles(&self) -> bool { self.contains(Self::MANAGE_ROLES) } /// Shorthand for checking that the set of permissions contains the /// [Manage Webhooks] permission. /// - /// [Manage Webhooks]: constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: #associatedconstant.MANAGE_WEBHOOKS pub fn manage_webhooks(&self) -> bool { self.contains(Self::MANAGE_WEBHOOKS) } /// Shorthand for checking that the set of permissions contains the /// [Mention Everyone] permission. /// - /// [Mention Everyone]: constant.MENTION_EVERYONE.html + /// [Mention Everyone]: #associatedconstant.MENTION_EVERYONE pub fn mention_everyone(&self) -> bool { self.contains(Self::MENTION_EVERYONE) } /// Shorthand for checking that the set of permissions contains the /// [Move Members] permission. /// - /// [Move Members]: constant.MOVE_MEMBERS.html + /// [Move Members]: #associatedconstant.MOVE_MEMBERS pub fn move_members(&self) -> bool { self.contains(Self::MOVE_MEMBERS) } /// Shorthand for checking that the set of permissions contains the /// [Mute Members] permission. /// - /// [Mute Members]: constant.MUTE_MEMBERS.html + /// [Mute Members]: #associatedconstant.MUTE_MEMBERS pub fn mute_members(&self) -> bool { self.contains(Self::MUTE_MEMBERS) } /// Shorthand for checking that the set of permissions contains the /// [Read Message History] permission. /// - /// [Read Message History]: constant.READ_MESSAGE_HISTORY.html + /// [Read Message History]: #associatedconstant.READ_MESSAGE_HISTORY pub fn read_message_history(&self) -> bool { self.contains(Self::READ_MESSAGE_HISTORY) } /// Shorthand for checking that the set of permissions contains the /// [Read Messages] permission. /// - /// [Read Messages]: constant.READ_MESSAGES.html + /// [Read Messages]: #associatedconstant.READ_MESSAGES pub fn read_messages(&self) -> bool { self.contains(Self::READ_MESSAGES) } /// Shorthand for checking that the set of permissions contains the /// [Send Messages] permission. /// - /// [Send Messages]: constant.SEND_MESSAGES.html + /// [Send Messages]: #associatedconstant.SEND_MESSAGES pub fn send_messages(&self) -> bool { self.contains(Self::SEND_MESSAGES) } /// Shorthand for checking that the set of permissions contains the /// [Send TTS Messages] permission. /// - /// [Send TTS Messages]: constant.SEND_TTS_MESSAGES.html + /// [Send TTS Messages]: #associatedconstant.SEND_TTS_MESSAGES pub fn send_tts_messages(&self) -> bool { self.contains(Self::SEND_TTS_MESSAGES) } /// Shorthand for checking that the set of permissions contains the /// [Speak] permission. /// - /// [Speak]: constant.SPEAK.html + /// [Speak]: #associatedconstant.SPEAK pub fn speak(&self) -> bool { self.contains(Self::SPEAK) } /// Shorthand for checking that the set of permissions contains the /// [Use External Emojis] permission. /// - /// [Use External Emojis]: constant.USE_EXTERNAL_EMOJIS.html + /// [Use External Emojis]: #associatedconstant.USE_EXTERNAL_EMOJIS pub fn use_external_emojis(&self) -> bool { self.contains(Self::USE_EXTERNAL_EMOJIS) } /// Shorthand for checking that the set of permissions contains the /// [Use VAD] permission. /// - /// [Use VAD]: constant.USE_VAD.html + /// [Use VAD]: #associatedconstant.USE_VAD pub fn use_vad(&self) -> bool { self.contains(Self::USE_VAD) } } diff --git a/src/model/user.rs b/src/model/user.rs index a378be5..019bec9 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -75,7 +75,7 @@ impl CurrentUser { /// /// This mutates the current user in-place. /// - /// Refer to `EditProfile`'s documentation for its methods. + /// Refer to [`EditProfile`]'s documentation for its methods. /// /// # Examples /// @@ -88,6 +88,8 @@ impl CurrentUser { /// /// CACHE.write().user.edit(|p| p.avatar(Some(&avatar))); /// ``` + /// + /// [`EditProfile`]: ../../builder/struct.EditProfile.html pub fn edit<F>(&mut self, f: F) -> Result<()> where F: FnOnce(EditProfile) -> EditProfile { let mut map = VecMap::new(); @@ -204,13 +206,13 @@ impl CurrentUser { /// # Errors /// /// Returns an - /// [`HttpError::InvalidRequest(Unauthorized)`][`HttpError::InvalidRequest`] + /// [`HttpError::UnsuccessfulRequest(Unauthorized)`][`HttpError::UnsuccessfulRequest`] /// If the user is not authorized for this end point. /// /// May return [`Error::Format`] while writing url to the buffer. /// - /// [`Error::Format`]: ../enum.Error.html#variant.Format - /// [`HttpError::InvalidRequest`]: ../http/enum.HttpError.html#variant.InvalidRequest + /// [`Error::Format`]: ../../enum.Error.html#variant.Format + /// [`HttpError::UnsuccessfulRequest`]: ../../http/enum.HttpError.html#variant.UnsuccessfulRequest pub fn invite_url(&self, permissions: Permissions) -> Result<String> { let bits = permissions.bits(); let client_id = http::get_current_application_info().map(|v| v.id)?; @@ -458,7 +460,7 @@ impl User { /// Returns a [`ModelError::MessagingBot`] if the user being direct messaged /// is a bot user. /// - /// [`ModelError::MessagingBot`]: enum.ModelError.html#variant.MessagingBot + /// [`ModelError::MessagingBot`]: ../error/enum.Error.html#variant.MessagingBot /// [`PrivateChannel`]: struct.PrivateChannel.html /// [`User::dm`]: struct.User.html#method.dm // A tale with Clippy: @@ -535,7 +537,7 @@ impl User { /// Returns a [`ModelError::MessagingBot`] if the user being direct messaged /// is a bot user. /// - /// [`ModelError::MessagingBot`]: enum.ModelError.html#variant.MessagingBot + /// [`ModelError::MessagingBot`]: ../error/enum.Error.html#variant.MessagingBot /// [direct_message]: #method.direct_message #[cfg(feature = "builder")] #[inline] @@ -572,11 +574,11 @@ impl User { /// let _ = message.author.has_role(guild_id, role_id); /// ``` /// - /// [`Guild`]: struct.Guild.html - /// [`GuildId`]: struct.GuildId.html - /// [`PartialGuild`]: struct.PartialGuild.html - /// [`Role`]: struct.Role.html - /// [`Cache`]: ../cache/struct.Cache.html + /// [`Guild`]: ../guild/struct.Guild.html + /// [`GuildId`]: ../id/struct.GuildId.html + /// [`PartialGuild`]: ../guild/struct.PartialGuild.html + /// [`Role`]: ../guild/struct.Role.html + /// [`Cache`]: ../../cache/struct.Cache.html // no-cache would warn on guild_id. pub fn has_role<G, R>(&self, guild: G, role: R) -> bool where G: Into<GuildContainer>, R: Into<RoleId> { @@ -725,7 +727,7 @@ impl UserId { /// Creates a direct message channel between the [current user] and the /// user. This can also retrieve the channel if one already exists. /// - /// [current user]: struct.CurrentUser.html + /// [current user]: ../user/struct.CurrentUser.html pub fn create_dm_channel(&self) -> Result<PrivateChannel> { let map = json!({ "recipient_id": self.0, @@ -738,9 +740,10 @@ impl UserId { #[cfg(feature = "cache")] pub fn find(&self) -> Option<Arc<RwLock<User>>> { CACHE.read().user(*self) } - /// Gets a user by its Id over the REST API. + /// Gets a user by its Id from either the cache or the REST API. /// - /// **Note**: The current user must be a bot user. + /// Searches the cache for the user first, if the cache is enabled. If the + /// user was not found, then the user is searched via the REST API. #[inline] pub fn get(&self) -> Result<User> { #[cfg(feature = "cache")] @@ -843,3 +846,71 @@ fn tag(name: &str, discriminator: u16) -> String { tag } + +#[cfg(test)] +mod test { + #[cfg(feature = "model")] + mod model { + use model::id::UserId; + use model::user::User; + + fn gen() -> User { + User { + id: UserId(210), + avatar: Some("abc".to_string()), + bot: true, + discriminator: 1432, + name: "test".to_string(), + } + } + + #[test] + fn test_core() { + let mut user = gen(); + + assert!( + user.avatar_url() + .unwrap() + .ends_with("/avatars/210/abc.webp?size=1024") + ); + assert!( + user.static_avatar_url() + .unwrap() + .ends_with("/avatars/210/abc.webp?size=1024") + ); + + user.avatar = Some("a_aaa".to_string()); + assert!( + user.avatar_url() + .unwrap() + .ends_with("/avatars/210/a_aaa.gif?size=1024") + ); + assert!( + user.static_avatar_url() + .unwrap() + .ends_with("/avatars/210/a_aaa.webp?size=1024") + ); + + user.avatar = None; + assert!(user.avatar_url().is_none()); + + assert_eq!(user.tag(), "test#1432"); + } + + #[test] + fn default_avatars() { + let mut user = gen(); + + user.discriminator = 0; + assert!(user.default_avatar_url().ends_with("0.png")); + user.discriminator = 1; + assert!(user.default_avatar_url().ends_with("1.png")); + user.discriminator = 2; + assert!(user.default_avatar_url().ends_with("2.png")); + user.discriminator = 3; + assert!(user.default_avatar_url().ends_with("3.png")); + user.discriminator = 4; + assert!(user.default_avatar_url().ends_with("4.png")); + } + } +} diff --git a/src/model/webhook.rs b/src/model/webhook.rs index 68d6012..fca70fe 100644 --- a/src/model/webhook.rs +++ b/src/model/webhook.rs @@ -33,7 +33,7 @@ pub struct Webhook { /// /// This can be modified via [`ExecuteWebhook::avatar`]. /// - /// [`ExecuteWebhook::avatar`]: ../builder/struct.ExecuteWebhook.html#method.avatar + /// [`ExecuteWebhook::avatar`]: ../../builder/struct.ExecuteWebhook.html#method.avatar pub avatar: Option<String>, /// The Id of the channel that owns the webhook. pub channel_id: ChannelId, @@ -43,7 +43,7 @@ pub struct Webhook { /// /// This can be modified via [`ExecuteWebhook::username`]. /// - /// [`ExecuteWebhook::username`]: ../builder/struct.ExecuteWebhook.html#method.username + /// [`ExecuteWebhook::username`]: ../../builder/struct.ExecuteWebhook.html#method.username pub name: Option<String>, /// The webhook's secure token. pub token: String, @@ -60,7 +60,7 @@ impl Webhook { /// As this calls the [`http::delete_webhook_with_token`] function, /// authentication is not required. /// - /// [`http::delete_webhook_with_token`]: ../http/fn.delete_webhook_with_token.html + /// [`http::delete_webhook_with_token`]: ../../http/fn.delete_webhook_with_token.html #[inline] pub fn delete(&self) -> Result<()> { http::delete_webhook_with_token(self.id.0, &self.token) } @@ -108,8 +108,8 @@ impl Webhook { /// let _ = webhook.edit(None, Some(&image)).expect("Error editing"); /// ``` /// - /// [`http::edit_webhook`]: ../http/fn.edit_webhook.html - /// [`http::edit_webhook_with_token`]: ../http/fn.edit_webhook_with_token.html + /// [`http::edit_webhook`]: ../../http/fn.edit_webhook.html + /// [`http::edit_webhook_with_token`]: ../../http/fn.edit_webhook_with_token.html pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) -> Result<()> { if name.is_none() && avatar.is_none() { return Ok(()); @@ -205,7 +205,7 @@ impl Webhook { /// As this calls the [`http::get_webhook_with_token`] function, /// authentication is not required. /// - /// [`http::get_webhook_with_token`]: ../http/fn.get_webhook_with_token.html + /// [`http::get_webhook_with_token`]: ../../http/fn.get_webhook_with_token.html pub fn refresh(&mut self) -> Result<()> { match http::get_webhook_with_token(self.id.0, &self.token) { Ok(replacement) => { @@ -224,7 +224,7 @@ impl WebhookId { /// /// **Note**: Requires the [Manage Webhooks] permission. /// - /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + /// [Manage Webhooks]: ../../model/permissions/struct.Permissions.html#associatedconstant.MANAGE_WEBHOOKS #[inline] pub fn get(self) -> Result<Webhook> { http::get_webhook(self.0) } } diff --git a/src/utils/colour.rs b/src/utils/colour.rs index c901e9b..a678a32 100644 --- a/src/utils/colour.rs +++ b/src/utils/colour.rs @@ -190,6 +190,22 @@ impl Colour { /// [`g`]: #method.g /// [`b`]: #method.b pub fn tuple(&self) -> (u8, u8, u8) { (self.r(), self.g(), self.b()) } + + /// Returns a hexadecimal string of this Colour. + /// + /// This is equivalent to passing the integer value through + /// `std::fmt::UpperHex` with 0 padding and 6 width + /// + /// # Examples + /// + /// ```rust + /// use serenity::utils::Colour; + /// + /// assert_eq!(Colour::new(6573123).hex(), "644C43"); + /// ``` + pub fn hex(&self) -> String { + format!("{:06X}", self.0) + } } impl From<i32> for Colour { @@ -309,3 +325,55 @@ impl Default for Colour { /// Creates a default value for a `Colour`, setting the inner value to `0`. fn default() -> Colour { Colour(0) } } + +#[cfg(test)] +mod test { + use super::Colour; + use std::u32; + + #[test] + fn new() { + assert_eq!(Colour::new(1).0, 1); + assert_eq!(Colour::new(u32::MIN).0, u32::MIN); + assert_eq!(Colour::new(u32::MAX).0, u32::MAX); + } + + #[test] + fn from_rgb() { + assert_eq!(Colour::from_rgb(255, 0, 0).0, 0xFF0000); + assert_eq!(Colour::from_rgb(0, 255, 0).0, 0x00FF00); + assert_eq!(Colour::from_rgb(0, 0, 255).0, 0x0000FF); + } + + #[test] + fn r() { + assert_eq!(Colour::new(0x336123).r(), 0x33); + } + + #[test] + fn g() { + assert_eq!(Colour::new(0x336123).g(), 0x61); + } + + #[test] + fn b() { + assert_eq!(Colour::new(0x336123).b(), 0x23); + } + + #[test] + fn tuple() { + assert_eq!(Colour::new(0x336123).tuple(), (0x33, 0x61, 0x23)); + } + + #[test] + fn default() { + assert_eq!(Colour::default().0, 0); + } + + #[test] + fn from() { + assert_eq!(Colour::from(7i32).0, 7); + assert_eq!(Colour::from(7u32).0, 7); + assert_eq!(Colour::from(7u64).0, 7); + } +} diff --git a/src/utils/message_builder.rs b/src/utils/message_builder.rs index 7bbfddf..c609c28 100644 --- a/src/utils/message_builder.rs +++ b/src/utils/message_builder.rs @@ -988,3 +988,81 @@ fn normalize(text: &str) -> String { .replace("@everyone", "@\u{200B}everyone") .replace("@here", "@\u{200B}here") } + +#[cfg(test)] +mod test { + use model::prelude::*; + use super::{ + ContentModifier::*, + MessageBuilder, + }; + + #[test] + fn code_blocks() { + let content = MessageBuilder::new() + .push_codeblock("test", Some("rb")) + .build(); + assert_eq!(content, "```rb\ntest\n```"); + } + + #[test] + fn safe_content() { + let content = MessageBuilder::new() + .push_safe("@everyone discord.gg/discord-api") + .build(); + assert_ne!(content, "@everyone discord.gg/discord-api"); + } + + #[test] + fn no_free_formatting() { + let content = MessageBuilder::new().push_bold_safe("test**test").build(); + assert_ne!(content, "**test**test**"); + } + + #[test] + fn mentions() { + let content_emoji = MessageBuilder::new() + .emoji(&Emoji { + animated: false, + id: EmojiId(32), + name: "Rohrkatze".to_string(), + managed: false, + require_colons: true, + roles: vec![], + }) + .build(); + let content_mentions = MessageBuilder::new() + .channel(1) + .mention(&UserId(2)) + .role(3) + .user(4) + .build(); + assert_eq!(content_mentions, "<#1><@2><@&3><@4>"); + assert_eq!(content_emoji, "<:Rohrkatze:32>"); + } + + #[test] + fn content() { + let content = Bold + Italic + Code + "Fun!"; + + assert_eq!(content.to_string(), "***`Fun!`***"); + } + + #[test] + fn message_content() { + let message_content = MessageBuilder::new() + .push(Bold + Italic + Code + "Fun!") + .build(); + + assert_eq!(message_content, "***`Fun!`***"); + } + + #[test] + fn message_content_safe() { + let message_content = MessageBuilder::new() + .push_safe(Bold + Italic + "test**test") + .build(); + + assert_eq!(message_content, "***test\\*\\*test***"); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e20dd22..347e19a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -131,7 +131,7 @@ pub fn is_nsfw(name: &str) -> bool { /// assert_eq!(utils::parse_invite(url), "0cDvIgU2voY8RSYL"); /// ``` /// -/// [`RichInvite`]: ../model/guild/struct.RichInvite.html +/// [`RichInvite`]: ../model/invite/struct.RichInvite.html pub fn parse_invite(code: &str) -> &str { if code.starts_with("https://discord.gg/") { &code[19..] @@ -500,3 +500,54 @@ pub fn with_cache_mut<T, F>(mut f: F) -> T let mut cache = CACHE.write(); f(&mut cache) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_invite_parser() { + assert_eq!(parse_invite("https://discord.gg/abc"), "abc"); + assert_eq!(parse_invite("http://discord.gg/abc"), "abc"); + assert_eq!(parse_invite("discord.gg/abc"), "abc"); + } + + #[test] + fn test_username_parser() { + assert_eq!(parse_username("<@12345>").unwrap(), 12_345); + assert_eq!(parse_username("<@!12345>").unwrap(), 12_345); + } + + #[test] + fn role_parser() { + assert_eq!(parse_role("<@&12345>").unwrap(), 12_345); + } + + #[test] + fn test_channel_parser() { + assert_eq!(parse_channel("<#12345>").unwrap(), 12_345); + } + + #[test] + fn test_emoji_parser() { + let emoji = parse_emoji("<:name:12345>").unwrap(); + assert_eq!(emoji.name, "name"); + assert_eq!(emoji.id, 12_345); + } + + #[test] + fn test_quote_parser() { + let parsed = parse_quotes("a \"b c\" d\"e f\" g"); + assert_eq!(parsed, ["a", "b c", "d", "e f", "g"]); + } + + #[test] + fn test_is_nsfw() { + assert!(!is_nsfw("general")); + assert!(is_nsfw("nsfw")); + assert!(is_nsfw("nsfw-test")); + assert!(!is_nsfw("nsfw-")); + assert!(!is_nsfw("général")); + assert!(is_nsfw("nsfw-général")); + } +} diff --git a/src/voice/audio.rs b/src/voice/audio.rs index 2e15590..c556c8b 100644 --- a/src/voice/audio.rs +++ b/src/voice/audio.rs @@ -6,7 +6,6 @@ use std::{ pub const HEADER_LEN: usize = 12; pub const SAMPLE_RATE: u32 = 48_000; -pub static SILENT_FRAME: [u8; 3] = [0xf8, 0xff, 0xfe]; /// A readable audio source. pub trait AudioSource: Send { diff --git a/src/voice/connection.rs b/src/voice/connection.rs index 54d9116..d1472d5 100644 --- a/src/voice/connection.rs +++ b/src/voice/connection.rs @@ -45,7 +45,7 @@ use std::{ }, time::Duration }; -use super::audio::{AudioReceiver, AudioType, LockedAudio, HEADER_LEN, SAMPLE_RATE, SILENT_FRAME}; +use super::audio::{AudioReceiver, AudioType, HEADER_LEN, SAMPLE_RATE, LockedAudio}; use super::connection_info::ConnectionInfo; use super::{payload, VoiceError, CRYPTO_MODE}; use websocket::{ @@ -200,8 +200,7 @@ impl Connection { keepalive_timer: Timer::new(temp_heartbeat), udp, sequence: 0, - // We need to send some frames to receive any audio. - silence_frames: 100, + silence_frames: 0, soft_clip, speaking: false, ssrc: hello.ssrc, @@ -393,7 +392,7 @@ impl Connection { self.silence_frames -= 1; // Explicit "Silence" frame. - opus_frame.extend_from_slice(&SILENT_FRAME); + opus_frame.extend_from_slice(&[0xf8, 0xff, 0xfe]); } else { // Per official guidelines, send 5x silence BEFORE we stop speaking. self.set_speaking(false)?; diff --git a/tests/resources/guild_-1_role_position.json b/tests/resources/guild_-1_role_position.json new file mode 100644 index 0000000..30898d1 --- /dev/null +++ b/tests/resources/guild_-1_role_position.json @@ -0,0 +1,271 @@ +{ + "voice_states": [], + "verification_level": 0, + "explicit_content_filter": 0, + "unavailable": false, + "splash": null, + "roles": [ + { + "position": -1, + "permissions": 37215297, + "name": "@everyone", + "mentionable": false, + "managed": false, + "id": "1", + "hoist": false, + "color": 0 + }, + { + "position": 1, + "permissions": 66583679, + "name": "role", + "mentionable": false, + "managed": false, + "id": "1", + "hoist": true, + "color": 7419530 + }, + { + "position": 2, + "permissions": 536345727, + "name": "role 2", + "mentionable": false, + "managed": false, + "id": "17", + "hoist": true, + "color": 2123412 + }, + { + "position": 3, + "permissions": 66583679, + "name": "role 3", + "mentionable": false, + "managed": false, + "id": "166", + "hoist": true, + "color": 3447003 + }, + { + "position": 1, + "permissions": 37215297, + "name": "aaaaaa", + "mentionable": true, + "managed": false, + "id": "88", + "hoist": false, + "color": 15277667 + }, + { + "position": 1, + "permissions": 35840, + "name": "aaaabsadfasda", + "mentionable": false, + "managed": true, + "id": "643534543", + "hoist": false, + "color": 0 + } + ], + "region": "us-central", + "presences": [ + { + "user": { + "id": "2342342" + }, + "status": "online", + "game": null + }, + { + "user": { + "id": "1233432" + }, + "status": "online", + "game": null + }, + { + "user": { + "id": "35353534" + }, + "status": "online", + "game": { + "url": "", + "type": 0, + "name": "aaaaaa" + } + }, + { + "user": { + "id": "12314324" + }, + "status": "online", + "game": null + } + ], + "owner_id": "7", + "name": "guild name", + "mfa_level": 0, + "members": [ + { + "user": { + "username": "aaa", + "id": "92781184873947136", + "discriminator": "6291", + "avatar": "asdasdadada" + }, + "roles": [ + "164155714355462146" + ], + "nick": "asdasdadas", + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + }, + { + "user": { + "username": "aaaaaa", + "id": "161972119494852608", + "discriminator": "7653", + "avatar": "ffffff" + }, + "roles": [ + "2342432423432" + ], + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + }, + { + "user": { + "username": "aaaaa", + "id": "167333834952540160", + "discriminator": "0857", + "bot": true, + "avatar": "ffffff" + }, + "roles": [ + "34534543543" + ], + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + }, + { + "user": { + "username": "aaaaaaa", + "id": "171403455745884160", + "discriminator": "0075", + "avatar": "ffffff" + }, + "roles": [ + "56465464" + ], + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + }, + { + "user": { + "username": "asdasdsadas", + "id": "12312312", + "discriminator": "7181", + "bot": true, + "avatar": "ffffff" + }, + "roles": [ + "12313212321" + ], + "nick": null, + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + }, + { + "user": { + "username": "aaaaa", + "id": "1231231231", + "discriminator": "2138", + "bot": true, + "avatar": "fake" + }, + "roles": [ + "1231231312" + ], + "nick": null, + "mute": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "deaf": false + } + ], + "member_count": 6, + "large": false, + "joined_at": "2017-01-29T15:35:17.136000+00:00", + "id": "12321321312321", + "icon": "fake icon", + "features": [], + "emojis": [], + "default_message_notifications": 0, + "channels": [ + { + "type": 0, + "topic": "", + "position": 0, + "permission_overwrites": [ + { + "type": "role", + "id": "123131231321", + "deny": 0, + "allow": 0 + } + ], + "name": "asdadsa", + "last_message_id": "5676576575", + "id": "3453543543" + }, + { + "user_limit": 0, + "type": 2, + "position": 0, + "permission_overwrites": [], + "name": "adssadasda", + "id": "56464564645", + "bitrate": 63841 + }, + { + "user_limit": 0, + "type": 2, + "position": 1, + "permission_overwrites": [ + { + "type": "role", + "id": "23423432423", + "deny": 2097152, + "allow": 0 + } + ], + "name": "AFK", + "id": "23432434242", + "bitrate": 64000 + }, + { + "type": 0, + "topic": null, + "position": 1, + "permission_overwrites": [], + "name": "asdasdasdsa", + "last_message_id": "234324324242", + "id": "2342343243242" + }, + { + "user_limit": 0, + "type": 2, + "position": 2, + "permission_overwrites": [], + "name": "asdadsa", + "id": "32134242342", + "bitrate": 96000 + } + ], + "afk_timeout": 900, + "afk_channel_id": "23432423423", + "system_channel_id": null +} diff --git a/tests/resources/role_-1_position.json b/tests/resources/role_-1_position.json new file mode 100644 index 0000000..7dca625 --- /dev/null +++ b/tests/resources/role_-1_position.json @@ -0,0 +1,10 @@ +{ + "position": -1, + "permissions": 37215297, + "name": "@everyone", + "mentionable": false, + "managed": false, + "id": "444", + "hoist": false, + "color": 0 +} diff --git a/tests/test_args.rs b/tests/test_args.rs deleted file mode 100644 index 9868b1b..0000000 --- a/tests/test_args.rs +++ /dev/null @@ -1,432 +0,0 @@ -extern crate serenity; -#[macro_use] extern crate matches; - -use serenity::framework::standard::{Args, ArgError}; - -#[test] -fn single_with_empty_message() { - let mut args = Args::new("", &["".to_string()]); - assert_matches!(args.single::<String>().unwrap_err(), ArgError::Eos); - - let mut args = Args::new("", &[",".to_string()]); - assert_matches!(args.single::<String>().unwrap_err(), ArgError::Eos); -} - -#[test] -fn single_n_with_empty_message() { - let args = Args::new("", &["".to_string()]); - assert_matches!(args.single_n::<String>().unwrap_err(), ArgError::Eos); - - let args = Args::new("", &[",".to_string()]); - assert_matches!(args.single_n::<String>().unwrap_err(), ArgError::Eos); -} - -#[test] -fn single_quoted_with_empty_message() { - let mut args = Args::new("", &["".to_string()]); - assert_matches!(args.single_quoted::<String>().unwrap_err(), ArgError::Eos); - - let mut args = Args::new("", &[",".to_string()]); - assert_matches!(args.single_quoted::<String>().unwrap_err(), ArgError::Eos); -} - -#[test] -fn multiple_with_empty_message() { - let args = Args::new("", &["".to_string()]); - assert_matches!(args.multiple::<String>().unwrap_err(), ArgError::Eos); - - let args = Args::new("", &[",".to_string()]); - assert_matches!(args.multiple::<String>().unwrap_err(), ArgError::Eos); -} - -#[test] -fn multiple_quoted_with_empty_message() { - let args = Args::new("", &["".to_string()]); - assert_matches!(args.multiple_quoted::<String>().unwrap_err(), ArgError::Eos); - - let args = Args::new("", &[",".to_string()]); - assert_matches!(args.multiple_quoted::<String>().unwrap_err(), ArgError::Eos); -} - -#[test] -fn skip_with_empty_message() { - let mut args = Args::new("", &["".to_string()]); - assert_matches!(args.skip(), None); - - let mut args = Args::new("", &[",".to_string()]); - assert_matches!(args.skip(), None); -} - -#[test] -fn skip_for_with_empty_message() { - let mut args = Args::new("", &["".to_string()]); - assert_matches!(args.skip_for(0), None); - - let mut args = Args::new("", &["".to_string()]); - assert_matches!(args.skip_for(5), None); - - let mut args = Args::new("", &[",".to_string()]); - assert_matches!(args.skip_for(0), None); - - let mut args = Args::new("", &[",".to_string()]); - assert_matches!(args.skip_for(5), None); -} - -#[test] -fn single_i32_with_2_bytes_long_delimiter() { - let mut args = Args::new("1, 2", &[", ".to_string()]); - - assert_eq!(args.single::<i32>().unwrap(), 1); - assert_eq!(args.single::<i32>().unwrap(), 2); -} - -#[test] -fn single_i32_with_1_byte_long_delimiter_i32() { - let mut args = Args::new("1,2", &[",".to_string()]); - - assert_eq!(args.single::<i32>().unwrap(), 1); - assert_eq!(args.single::<i32>().unwrap(), 2); -} - -#[test] -fn single_i32_with_wrong_char_after_first_arg() { - let mut args = Args::new("1, 2", &[",".to_string()]); - - assert_eq!(args.single::<i32>().unwrap(), 1); - assert!(args.single::<i32>().is_err()); -} - -#[test] -fn single_i32_with_one_character_being_3_bytes_long() { - let mut args = Args::new("1★2", &["★".to_string()]); - - assert_eq!(args.single::<i32>().unwrap(), 1); - assert_eq!(args.single::<i32>().unwrap(), 2); -} - -#[test] -fn single_i32_with_untrimmed_whitespaces() { - let mut args = Args::new(" 1, 2 ", &[",".to_string()]); - - assert!(args.single::<i32>().is_err()); -} - -#[test] -fn single_i32_n() { - let args = Args::new("1,2", &[",".to_string()]); - - assert_eq!(args.single_n::<i32>().unwrap(), 1); - assert_eq!(args.single_n::<i32>().unwrap(), 1); -} - -#[test] -fn single_quoted_chaining() { - let mut args = Args::new(r#""1, 2" "2" """#, &[" ".to_string()]); - - assert_eq!(args.single_quoted::<String>().unwrap(), "1, 2"); - assert_eq!(args.single_quoted::<String>().unwrap(), "2"); - assert_eq!(args.single_quoted::<String>().unwrap(), ""); -} - -#[test] -fn single_quoted_and_single_chaining() { - let mut args = Args::new(r#""1, 2" "2" "3" 4"#, &[" ".to_string()]); - - assert_eq!(args.single_quoted::<String>().unwrap(), "1, 2"); - assert!(args.single_n::<i32>().is_err()); - assert_eq!(args.single::<String>().unwrap(), "\"2\""); - assert_eq!(args.single_quoted::<i32>().unwrap(), 3); - assert_eq!(args.single::<i32>().unwrap(), 4); -} - -#[test] -fn full_on_args() { - let test_text = "Some text to ensure `full()` works."; - let args = Args::new(test_text, &[" ".to_string()]); - - assert_eq!(args.full(), test_text); -} - -#[test] -fn multiple_quoted_strings_one_delimiter() { - let args = Args::new(r#""1, 2" "a" "3" 4 "5"#, &[" ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", "4", "\"5"]); -} - -#[test] -fn multiple_quoted_strings_with_multiple_delimiter() { - let args = Args::new(r#""1, 2" "a","3"4 "5"#, &[" ".to_string(), ",".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", "4", "\"5"]); -} - -#[test] -fn multiple_quoted_strings_with_multiple_delimiters() { - let args = Args::new(r#""1, 2" "a","3" """#, &[" ".to_string(), ",".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["1, 2", "a", "3", ""]); -} - -#[test] -fn multiple_quoted_i32() { - let args = Args::new(r#""1" "2" 3"#, &[" ".to_string()]); - - assert_eq!(args.multiple_quoted::<i32>().unwrap(), [1, 2, 3]); -} - -#[test] -fn multiple_quoted_quote_appears_without_delimiter_in_front() { - let args = Args::new(r#"hello, my name is cake" 2"#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "my", "name", "is", "cake\"", "2"]); -} - -#[test] -fn multiple_quoted_single_quote() { - let args = Args::new(r#"hello "2 b"#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "\"2 b"]); -} - -#[test] -fn multiple_quoted_one_quote_pair() { - let args = Args::new(r#"hello "2 b""#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello", "2 b"]); -} - - -#[test] -fn delimiter_before_multiple_quoted() { - let args = Args::new(r#","hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); -} - -#[test] -fn no_quote() { - let args = Args::new("hello, my name is cake", &[",".to_string(), " ".to_string()]); - - assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello"); -} - -#[test] -fn single_quoted_n() { - let args = Args::new(r#""hello, my name is cake","test"#, &[",".to_string()]); - - assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello, my name is cake"); - assert_eq!(args.single_quoted_n::<String>().unwrap(), "hello, my name is cake"); -} - -#[test] -fn multiple_quoted_starting_with_wrong_delimiter_in_first_quote() { - let args = Args::new(r#""hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); -} - -#[test] -fn multiple_quoted_with_one_correct_and_one_invalid_quote() { - let args = Args::new(r#""hello, my name is cake" "2""#, &[",".to_string(), " ".to_string()]); - - assert_eq!(args.multiple_quoted::<String>().unwrap(), ["hello, my name is cake", "2"]); -} - -#[test] -fn find_i32_one_one_byte_delimiter() { - let mut args = Args::new("hello,my name is cake 2", &[" ".to_string()]); - - assert_eq!(args.find::<i32>().unwrap(), 2); -} - -#[test] -fn find_i32_one_three_byte_delimiter() { - let mut args = Args::new("hello,my name is cakeé2", &["é".to_string()]); - - assert_eq!(args.find::<i32>().unwrap(), 2); -} - -#[test] -fn find_i32_multiple_delimiter_but_i32_not_last() { - let mut args = Args::new("hello,my name is 2 cake", &[" ".to_string(), ",".to_string()]); - - assert_eq!(args.find::<i32>().unwrap(), 2); -} - -#[test] -fn find_i32_multiple_delimiter() { - let mut args = Args::new("hello,my name is cake 2", &[" ".to_string(), ",".to_string()]); - - assert_eq!(args.find::<i32>().unwrap(), 2); -} - -#[test] -fn find_n_i32() { - let mut args = Args::new("a 2", &[" ".to_string()]); - - assert_eq!(args.find_n::<i32>().unwrap(), 2); - assert_eq!(args.find_n::<i32>().unwrap(), 2); -} - -#[test] -fn skip() { - let mut args = Args::new("1 2", &[" ".to_string()]); - - assert_eq!(args.skip().unwrap(), "1"); - assert_eq!(args.remaining(), 1); - assert_eq!(args.single::<String>().unwrap(), "2"); -} - -#[test] -fn skip_for() { - let mut args = Args::new("1 2 neko 100", &[" ".to_string()]); - - assert_eq!(args.skip_for(2).unwrap(), ["1", "2"]); - assert_eq!(args.remaining(), 2); - assert_eq!(args.single::<String>().unwrap(), "neko"); - assert_eq!(args.single::<String>().unwrap(), "100"); -} - -#[test] -fn len_with_one_delimiter() { - let args = Args::new("1 2 neko 100", &[" ".to_string()]); - - assert_eq!(args.len(), 4); - assert_eq!(args.remaining(), 4); -} - -#[test] -fn len_multiple_quoted() { - let args = Args::new(r#""hello, my name is cake" "2""#, &[" ".to_string()]); - - assert_eq!(args.len(), 2); -} - -#[test] -fn remaining_len_before_and_after_single() { - let mut args = Args::new("1 2", &[" ".to_string()]); - - assert_eq!(args.remaining(), 2); - assert_eq!(args.single::<i32>().unwrap(), 1); - assert_eq!(args.remaining(), 1); - assert_eq!(args.single::<i32>().unwrap(), 2); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_single_quoted() { - let mut args = Args::new(r#""1" "2" "3""#, &[" ".to_string()]); - - assert_eq!(args.remaining(), 3); - assert_eq!(args.single_quoted::<i32>().unwrap(), 1); - assert_eq!(args.remaining(), 2); - assert_eq!(args.single_quoted::<i32>().unwrap(), 2); - assert_eq!(args.remaining(), 1); - assert_eq!(args.single_quoted::<i32>().unwrap(), 3); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_skip() { - let mut args = Args::new("1 2", &[" ".to_string()]); - - assert_eq!(args.remaining(), 2); - assert_eq!(args.skip().unwrap(), "1"); - assert_eq!(args.remaining(), 1); - assert_eq!(args.skip().unwrap(), "2"); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_skip_empty_string() { - let mut args = Args::new("", &[" ".to_string()]); - - assert_eq!(args.remaining(), 0); - assert_eq!(args.skip(), None); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_skip_for() { - let mut args = Args::new("1 2", &[" ".to_string()]); - - assert_eq!(args.remaining(), 2); - assert_eq!(args.skip_for(2), Some(vec!["1".to_string(), "2".to_string()])); - assert_eq!(args.skip_for(2), None); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_find() { - let mut args = Args::new("a 2 6", &[" ".to_string()]); - - assert_eq!(args.remaining(), 3); - assert_eq!(args.find::<i32>().unwrap(), 2); - assert_eq!(args.remaining(), 2); - assert_eq!(args.find::<i32>().unwrap(), 6); - assert_eq!(args.remaining(), 1); - assert_eq!(args.find::<String>().unwrap(), "a"); - assert_eq!(args.remaining(), 0); - assert_matches!(args.find::<String>().unwrap_err(), ArgError::Eos); - assert_eq!(args.remaining(), 0); -} - -#[test] -fn remaining_len_before_and_after_find_n() { - let mut args = Args::new("a 2 6", &[" ".to_string()]); - - assert_eq!(args.remaining(), 3); - assert_eq!(args.find_n::<i32>().unwrap(), 2); - assert_eq!(args.remaining(), 3); -} - - -#[test] -fn multiple_strings_with_one_delimiter() { - let args = Args::new("hello, my name is cake 2", &[" ".to_string()]); - - assert_eq!(args.multiple::<String>().unwrap(), ["hello,", "my", "name", "is", "cake", "2"]); -} - -#[test] -fn multiple_i32_with_one_delimiter() { - let args = Args::new("1 2 3", &[" ".to_string()]); - - assert_eq!(args.multiple::<i32>().unwrap(), [1, 2, 3]); -} - -#[test] -fn multiple_i32_with_one_delimiter_and_parse_error() { - let args = Args::new("1 2 3 abc", &[" ".to_string()]); - - assert_matches!(args.multiple::<i32>().unwrap_err(), ArgError::Parse(_)); -} - -#[test] -fn multiple_i32_with_three_delimiters() { - let args = Args::new("1 2 3", &[" ".to_string(), ",".to_string()]); - - assert_eq!(args.multiple::<i32>().unwrap(), [1, 2, 3]); -} - -#[test] -fn single_after_failed_single() { - let mut args = Args::new("b 2", &[" ".to_string()]); - - assert_matches!(args.single::<i32>().unwrap_err(), ArgError::Parse(_)); - // Test that `single` short-circuts on an error and leaves the source as is. - assert_eq!(args.remaining(), 2); - assert_eq!(args.single::<String>().unwrap(), "b"); - assert_eq!(args.single::<String>().unwrap(), "2"); -} - -#[test] -fn remaining_len_after_failed_single_quoted() { - let mut args = Args::new("b a", &[" ".to_string()]); - - assert_eq!(args.remaining(), 2); - // Same goes for `single_quoted` and the alike. - assert_matches!(args.single_quoted::<i32>().unwrap_err(), ArgError::Parse(_)); - assert_eq!(args.remaining(), 2); -} diff --git a/tests/test_cache.rs b/tests/test_cache.rs deleted file mode 100644 index 833af04..0000000 --- a/tests/test_cache.rs +++ /dev/null @@ -1,174 +0,0 @@ -#![cfg(feature = "cache")]
-
-extern crate chrono;
-extern crate serde_json;
-extern crate serenity;
-
-use chrono::DateTime;
-use serde_json::{Number, Value};
-use serenity::{
- cache::{Cache, CacheUpdate, Settings},
- model::prelude::*,
- prelude::RwLock,
-};
-use std::{
- collections::HashMap,
- sync::Arc,
-};
-
-#[test]
-fn test_cache_messages() {
- let mut settings = Settings::new();
- settings.max_messages(2);
- let mut cache = Cache::new_with_settings(settings);
-
- // Test inserting one message into a channel's message cache.
- let datetime = DateTime::parse_from_str(
- "1983 Apr 13 12:09:14.274 +0000",
- "%Y %b %d %H:%M:%S%.3f %z",
- ).unwrap();
- let mut event = MessageCreateEvent {
- message: Message {
- id: MessageId(3),
- attachments: vec![],
- author: User {
- id: UserId(2),
- avatar: None,
- bot: false,
- discriminator: 1,
- name: "user 1".to_owned(),
- },
- channel_id: ChannelId(2),
- guild_id: Some(GuildId(1)),
- content: String::new(),
- edited_timestamp: None,
- embeds: vec![],
- kind: MessageType::Regular,
- member: None,
- mention_everyone: false,
- mention_roles: vec![],
- mentions: vec![],
- nonce: Value::Number(Number::from(1)),
- pinned: false,
- reactions: vec![],
- timestamp: datetime.clone(),
- tts: false,
- webhook_id: None,
- },
- };
- // Check that the channel cache doesn't exist.
- assert!(!cache.messages.contains_key(&event.message.channel_id));
- // Add first message, none because message ID 2 doesn't already exist.
- assert!(event.update(&mut cache).is_none());
- // None, it only returns the oldest message if the cache was already full.
- assert!(event.update(&mut cache).is_none());
- // Assert there's only 1 message in the channel's message cache.
- assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 1);
-
- // Add a second message, assert that channel message cache length is 2.
- event.message.id = MessageId(4);
- assert!(event.update(&mut cache).is_none());
- assert_eq!(cache.messages.get(&event.message.channel_id).unwrap().len(), 2);
-
- // Add a third message, the first should now be removed.
- event.message.id = MessageId(5);
- assert!(event.update(&mut cache).is_some());
-
- {
- let channel = cache.messages.get(&event.message.channel_id).unwrap();
-
- assert_eq!(channel.len(), 2);
- // Check that the first message is now removed.
- assert!(!channel.contains_key(&MessageId(3)));
- }
-
- let guild_channel = GuildChannel {
- id: event.message.channel_id,
- bitrate: None,
- category_id: None,
- guild_id: event.message.guild_id.unwrap(),
- kind: ChannelType::Text,
- last_message_id: None,
- last_pin_timestamp: None,
- name: String::new(),
- permission_overwrites: vec![],
- position: 0,
- topic: None,
- user_limit: None,
- nsfw: false,
- };
-
- // Add a channel delete event to the cache, the cached messages for that
- // channel should now be gone.
- let mut delete = ChannelDeleteEvent {
- channel: Channel::Guild(Arc::new(RwLock::new(guild_channel.clone()))),
- };
- assert!(cache.update(&mut delete).is_none());
- assert!(!cache.messages.contains_key(&delete.channel.id()));
-
- // Test deletion of a guild channel's message cache when a GuildDeleteEvent
- // is received.
- let mut guild_create = {
- let mut channels = HashMap::new();
- channels.insert(ChannelId(2), Arc::new(RwLock::new(guild_channel.clone())));
-
- GuildCreateEvent {
- guild: Guild {
- id: GuildId(1),
- afk_channel_id: None,
- afk_timeout: 0,
- application_id: None,
- default_message_notifications: DefaultMessageNotificationLevel::All,
- emojis: HashMap::new(),
- explicit_content_filter: ExplicitContentFilter::None,
- features: vec![],
- icon: None,
- joined_at: datetime,
- large: false,
- member_count: 0,
- members: HashMap::new(),
- mfa_level: MfaLevel::None,
- name: String::new(),
- owner_id: UserId(3),
- presences: HashMap::new(),
- region: String::new(),
- roles: HashMap::new(),
- splash: None,
- system_channel_id: None,
- verification_level: VerificationLevel::Low,
- voice_states: HashMap::new(),
- channels,
- },
- }
- };
- assert!(cache.update(&mut guild_create).is_none());
- assert!(cache.update(&mut event).is_none());
-
- let mut guild_delete = GuildDeleteEvent {
- guild: PartialGuild {
- id: GuildId(1),
- afk_channel_id: None,
- afk_timeout: 0,
- default_message_notifications: DefaultMessageNotificationLevel::All,
- embed_channel_id: None,
- embed_enabled: false,
- emojis: HashMap::new(),
- features: vec![],
- icon: None,
- mfa_level: MfaLevel::None,
- name: String::new(),
- owner_id: UserId(3),
- region: String::new(),
- roles: HashMap::new(),
- splash: None,
- verification_level: VerificationLevel::Low,
- },
- };
-
- // The guild existed in the cache, so the cache's guild is returned by the
- // update.
- assert!(cache.update(&mut guild_delete).is_some());
-
- // Assert that the channel's message cache no longer exists.
- assert!(!cache.messages.contains_key(&ChannelId(2)));
-}
diff --git a/tests/test_channels.rs b/tests/test_channels.rs deleted file mode 100644 index 92e31fc..0000000 --- a/tests/test_channels.rs +++ /dev/null @@ -1,91 +0,0 @@ -#![cfg(feature = "model")] - -extern crate parking_lot; -extern crate serenity; - -#[cfg(feature = "utils")] -mod utils { - use parking_lot::RwLock; - use serenity::model::prelude::*; - use std::collections::HashMap; - use std::sync::Arc; - - fn group() -> Group { - Group { - channel_id: ChannelId(1), - icon: None, - last_message_id: None, - last_pin_timestamp: None, - name: None, - owner_id: UserId(2), - recipients: HashMap::new(), - } - } - - fn guild_channel() -> GuildChannel { - GuildChannel { - id: ChannelId(1), - bitrate: None, - category_id: None, - guild_id: GuildId(2), - kind: ChannelType::Text, - last_message_id: None, - last_pin_timestamp: None, - name: "nsfw-stuff".to_string(), - permission_overwrites: vec![], - position: 0, - topic: None, - user_limit: None, - nsfw: false, - } - } - - fn private_channel() -> PrivateChannel { - PrivateChannel { - id: ChannelId(1), - last_message_id: None, - last_pin_timestamp: None, - kind: ChannelType::Private, - recipient: Arc::new(RwLock::new(User { - id: UserId(2), - avatar: None, - bot: false, - discriminator: 1, - name: "ab".to_string(), - })), - } - } - - #[test] - fn nsfw_checks() { - let mut channel = guild_channel(); - assert!(channel.is_nsfw()); - channel.kind = ChannelType::Voice; - assert!(!channel.is_nsfw()); - - channel.kind = ChannelType::Text; - channel.name = "nsfw-".to_string(); - assert!(!channel.is_nsfw()); - - channel.name = "nsfw".to_string(); - assert!(channel.is_nsfw()); - channel.kind = ChannelType::Voice; - assert!(!channel.is_nsfw()); - channel.kind = ChannelType::Text; - - channel.name = "nsf".to_string(); - channel.nsfw = true; - assert!(channel.is_nsfw()); - channel.nsfw = false; - assert!(!channel.is_nsfw()); - - let channel = Channel::Guild(Arc::new(RwLock::new(channel))); - assert!(!channel.is_nsfw()); - - let group = group(); - assert!(!group.is_nsfw()); - - let private_channel = private_channel(); - assert!(!private_channel.is_nsfw()); - } -} diff --git a/tests/test_colour.rs b/tests/test_colour.rs deleted file mode 100644 index 2b36562..0000000 --- a/tests/test_colour.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] -#![cfg(feature = "utils")] - -extern crate serenity; - -use serenity::utils::Colour; -use std::u32; - -#[test] -fn new() { - assert_eq!(Colour::new(1).0, 1); - assert_eq!(Colour::new(u32::MIN).0, u32::MIN); - assert_eq!(Colour::new(u32::MAX).0, u32::MAX); -} - -#[test] -fn from_rgb() { - assert_eq!(Colour::from_rgb(255, 0, 0).0, 0xFF0000); - assert_eq!(Colour::from_rgb(0, 255, 0).0, 0x00FF00); - assert_eq!(Colour::from_rgb(0, 0, 255).0, 0x0000FF); -} - -#[test] -fn r() { - assert_eq!(Colour::new(0x336123).r(), 0x33); -} - -#[test] -fn g() { - assert_eq!(Colour::new(0x336123).g(), 0x61); -} - -#[test] -fn b() { - assert_eq!(Colour::new(0x336123).b(), 0x23); -} - -#[test] -fn tuple() { - assert_eq!(Colour::new(0x336123).tuple(), (0x33, 0x61, 0x23)); -} - -#[test] -fn default() { - assert_eq!(Colour::default().0, 0); -} - -#[test] -fn from() { - assert_eq!(Colour::from(7i32).0, 7); - assert_eq!(Colour::from(7u32).0, 7); - assert_eq!(Colour::from(7u64).0, 7); -} diff --git a/tests/test_create_embed.rs b/tests/test_create_embed.rs deleted file mode 100644 index 5bdce5f..0000000 --- a/tests/test_create_embed.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))] -#![cfg(all(feature = "builder", feature = "utils"))] - -#[macro_use] -extern crate serde_json; -extern crate serenity; - -use serde_json::Value; -use serenity::model::channel::{Embed, EmbedField, EmbedFooter, EmbedImage, EmbedVideo}; -use serenity::builder::CreateEmbed; -use serenity::utils::{self, Colour}; - -#[test] -fn test_from_embed() { - let embed = Embed { - author: None, - colour: Colour::new(0xFF0011), - description: Some("This is a test description".to_string()), - fields: vec![ - EmbedField { - inline: false, - name: "a".to_string(), - value: "b".to_string(), - }, - EmbedField { - inline: true, - name: "c".to_string(), - value: "z".to_string(), - }, - ], - footer: Some(EmbedFooter { - icon_url: Some("https://i.imgur.com/XfWpfCV.gif".to_string()), - proxy_icon_url: None, - text: "This is a hakase footer".to_string(), - }), - image: Some(EmbedImage { - height: 213, - proxy_url: "a".to_string(), - url: "https://i.imgur.com/XfWpfCV.gif".to_string(), - width: 224, - }), - kind: "rich".to_string(), - provider: None, - thumbnail: None, - timestamp: None, - title: Some("hakase".to_string()), - url: Some("https://i.imgur.com/XfWpfCV.gif".to_string()), - video: Some(EmbedVideo { - height: 213, - url: "https://i.imgur.com/XfWpfCV.mp4".to_string(), - width: 224, - }), - }; - - let builder = CreateEmbed::from(embed) - .colour(0xFF0011) - .description("This is a hakase description") - .image("https://i.imgur.com/XfWpfCV.gif") - .title("still a hakase") - .url("https://i.imgur.com/XfWpfCV.gif"); - - let built = Value::Object(utils::vecmap_to_json_map(builder.0)); - - let obj = json!({ - "color": 0xFF0011, - "description": "This is a hakase description", - "title": "still a hakase", - "type": "rich", - "url": "https://i.imgur.com/XfWpfCV.gif", - "fields": [ - { - "inline": false, - "name": "a", - "value": "b", - }, - { - "inline": true, - "name": "c", - "value": "z", - }, - ], - "image": { - "url": "https://i.imgur.com/XfWpfCV.gif", - }, - "footer": { - "text": "This is a hakase footer", - "icon_url": "https://i.imgur.com/XfWpfCV.gif", - } - }); - - assert_eq!(built, obj); -} diff --git a/tests/test_decode_role.rs b/tests/test_decode_role.rs deleted file mode 100644 index 61e0e13..0000000 --- a/tests/test_decode_role.rs +++ /dev/null @@ -1,302 +0,0 @@ -extern crate serde_json; -extern crate serenity; - -use serde_json::Value; -use serenity::model::guild::{Guild, Role}; - -#[test] -fn decode_negative_one_role_position() { - let json = r#"{ - "position": -1, - "permissions": 37215297, - "name": "@everyone", - "mentionable": false, - "managed": false, - "id": "444", - "hoist": false, - "color": 0 - }"#; - - let value: Value = serde_json::from_str(json).unwrap(); - - serde_json::from_value::<Role>(value).unwrap(); -} - -#[test] -fn decode_guild_with_n1_role_position() { - let json = r#"{ - "voice_states": [], - "verification_level": 0, - "explicit_content_filter": 0, - "unavailable": false, - "splash": null, - "roles": [ - { - "position": -1, - "permissions": 37215297, - "name": "@everyone", - "mentionable": false, - "managed": false, - "id": "1", - "hoist": false, - "color": 0 - }, - { - "position": 1, - "permissions": 66583679, - "name": "role", - "mentionable": false, - "managed": false, - "id": "1", - "hoist": true, - "color": 7419530 - }, - { - "position": 2, - "permissions": 536345727, - "name": "role 2", - "mentionable": false, - "managed": false, - "id": "17", - "hoist": true, - "color": 2123412 - }, - { - "position": 3, - "permissions": 66583679, - "name": "role 3", - "mentionable": false, - "managed": false, - "id": "166", - "hoist": true, - "color": 3447003 - }, - { - "position": 1, - "permissions": 37215297, - "name": "aaaaaa", - "mentionable": true, - "managed": false, - "id": "88", - "hoist": false, - "color": 15277667 - }, - { - "position": 1, - "permissions": 35840, - "name": "aaaabsadfasda", - "mentionable": false, - "managed": true, - "id": "643534543", - "hoist": false, - "color": 0 - } - ], - "region": "us-central", - "presences": [ - { - "user": { - "id": "2342342" - }, - "status": "online", - "game": null - }, - { - "user": { - "id": "1233432" - }, - "status": "online", - "game": null - }, - { - "user": { - "id": "35353534" - }, - "status": "online", - "game": { - "url": "", - "type": 0, - "name": "aaaaaa" - } - }, - { - "user": { - "id": "12314324" - }, - "status": "online", - "game": null - } - ], - "owner_id": "7", - "name": "guild name", - "mfa_level": 0, - "members": [ - { - "user": { - "username": "aaa", - "id": "92781184873947136", - "discriminator": "6291", - "avatar": "asdasdadada" - }, - "roles": [ - "164155714355462146" - ], - "nick": "asdasdadas", - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - }, - { - "user": { - "username": "aaaaaa", - "id": "161972119494852608", - "discriminator": "7653", - "avatar": "ffffff" - }, - "roles": [ - "2342432423432" - ], - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - }, - { - "user": { - "username": "aaaaa", - "id": "167333834952540160", - "discriminator": "0857", - "bot": true, - "avatar": "ffffff" - }, - "roles": [ - "34534543543" - ], - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - }, - { - "user": { - "username": "aaaaaaa", - "id": "171403455745884160", - "discriminator": "0075", - "avatar": "ffffff" - }, - "roles": [ - "56465464" - ], - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - }, - { - "user": { - "username": "asdasdsadas", - "id": "12312312", - "discriminator": "7181", - "bot": true, - "avatar": "ffffff" - }, - "roles": [ - "12313212321" - ], - "nick": null, - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - }, - { - "user": { - "username": "aaaaa", - "id": "1231231231", - "discriminator": "2138", - "bot": true, - "avatar": "fake" - }, - "roles": [ - "1231231312" - ], - "nick": null, - "mute": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "deaf": false - } - ], - "member_count": 6, - "large": false, - "joined_at": "2017-01-29T15:35:17.136000+00:00", - "id": "12321321312321", - "icon": "fake icon", - "features": [], - "emojis": [], - "default_message_notifications": 0, - "channels": [ - { - "type": 0, - "topic": "", - "position": 0, - "permission_overwrites": [ - { - "type": "role", - "id": "123131231321", - "deny": 0, - "allow": 0 - } - ], - "name": "asdadsa", - "last_message_id": "5676576575", - "id": "3453543543" - }, - { - "user_limit": 0, - "type": 2, - "position": 0, - "permission_overwrites": [], - "name": "adssadasda", - "id": "56464564645", - "bitrate": 63841 - }, - { - "user_limit": 0, - "type": 2, - "position": 1, - "permission_overwrites": [ - { - "type": "role", - "id": "23423432423", - "deny": 2097152, - "allow": 0 - } - ], - "name": "AFK", - "id": "23432434242", - "bitrate": 64000 - }, - { - "type": 0, - "topic": null, - "position": 1, - "permission_overwrites": [], - "name": "asdasdasdsa", - "last_message_id": "234324324242", - "id": "2342343243242" - }, - { - "user_limit": 0, - "type": 2, - "position": 2, - "permission_overwrites": [], - "name": "asdadsa", - "id": "32134242342", - "bitrate": 96000 - } - ], - "afk_timeout": 900, - "afk_channel_id": "23432423423", - "system_channel_id": null - }"#; - - let value: Value = serde_json::from_str(json).unwrap(); - - serde_json::from_value::<Guild>(value).unwrap(); -} diff --git a/tests/test_deser.rs b/tests/test_deser.rs index 70b6f1f..72c955d 100644 --- a/tests/test_deser.rs +++ b/tests/test_deser.rs @@ -8,11 +8,12 @@ use serenity::model::prelude::*; use std::fs::File; macro_rules! p { - ($s:ident, $filename:expr) => { + ($s:ident, $filename:expr) => {{ let f = File::open(concat!("./tests/resources/", $filename, ".json")).unwrap(); let v = serde_json::from_reader::<File, Value>(f).unwrap(); - let _ = $s::deserialize(v).unwrap(); - }; + + $s::deserialize(v).unwrap() + }}; } #[test] @@ -193,3 +194,25 @@ fn guild_features_deser() { fn guild_system_channel_id_missing() { p!(Guild, "guild_system_channel_id_missing"); } + +#[test] +fn decode_negative_one_role_position() { + p!(Role, "role_-1_position"); +} + +#[test] +fn decode_guild_with_n1_role_position() { + p!(Guild, "guild_-1_role_position"); +} + +#[test] +fn decode_footer_deser() { + let mut message = p!(Message, "message_footer_1"); + + assert_eq!( + message.embeds.remove(0).footer.unwrap().text, + "2005-09-26 - 2013-09-26" + ); + + p!(Message, "message_footer_2"); +} diff --git a/tests/test_formatters.rs b/tests/test_formatters.rs deleted file mode 100644 index ccdfba1..0000000 --- a/tests/test_formatters.rs +++ /dev/null @@ -1,80 +0,0 @@ -extern crate parking_lot; -extern crate serenity; - -use serenity::model::prelude::*; - -#[test] -fn test_formatters() { - assert_eq!(ChannelId(1).to_string(), "1"); - assert_eq!(EmojiId(2).to_string(), "2"); - assert_eq!(GuildId(3).to_string(), "3"); - assert_eq!(RoleId(4).to_string(), "4"); - assert_eq!(UserId(5).to_string(), "5"); -} - -#[cfg(feature = "utils")] -#[test] -fn test_mention() { - use parking_lot::RwLock; - use serenity::utils::Colour; - use std::sync::Arc; - - let channel = Channel::Guild(Arc::new(RwLock::new(GuildChannel { - bitrate: None, - category_id: None, - guild_id: GuildId(1), - kind: ChannelType::Text, - id: ChannelId(4), - last_message_id: None, - last_pin_timestamp: None, - name: "a".to_string(), - permission_overwrites: vec![], - position: 1, - topic: None, - user_limit: None, - nsfw: false, - }))); - let emoji = Emoji { - animated: false, - id: EmojiId(5), - name: "a".to_string(), - managed: true, - require_colons: true, - roles: vec![], - }; - let role = Role { - id: RoleId(2), - colour: Colour::ROSEWATER, - hoist: false, - managed: false, - mentionable: false, - name: "fake role".to_string(), - permissions: Permissions::empty(), - position: 1, - }; - let user = User { - id: UserId(6), - avatar: None, - bot: false, - discriminator: 4132, - name: "fake".to_string(), - }; - let member = Member { - deaf: false, - guild_id: GuildId(2), - joined_at: None, - mute: false, - nick: None, - roles: vec![], - user: Arc::new(RwLock::new(user.clone())), - }; - - assert_eq!(ChannelId(1).mention(), "<#1>"); - assert_eq!(channel.mention(), "<#4>"); - assert_eq!(emoji.mention(), "<:a:5>"); - assert_eq!(member.mention(), "<@6>"); - assert_eq!(role.mention(), "<@&2>"); - assert_eq!(role.id.mention(), "<@&2>"); - assert_eq!(user.mention(), "<@6>"); - assert_eq!(user.id.mention(), "<@6>"); -} diff --git a/tests/test_guild.rs b/tests/test_guild.rs deleted file mode 100644 index 1045381..0000000 --- a/tests/test_guild.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![cfg(feature = "model")] - -extern crate chrono; -extern crate serenity; - -use chrono::prelude::*; -use serenity::model::prelude::*; -use serenity::prelude::*; -use std::collections::*; -use std::sync::Arc; - -fn gen_user() -> User { - User { - id: UserId(210), - avatar: Some("abc".to_string()), - bot: true, - discriminator: 1432, - name: "test".to_string(), - } -} - -fn gen_member() -> Member { - let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600) - .ymd(2016, 11, 08) - .and_hms(0, 0, 0); - let vec1 = Vec::new(); - let u = Arc::new(RwLock::new(gen_user())); - - Member { - deaf: false, - guild_id: GuildId(1), - joined_at: Some(dt), - mute: false, - nick: Some("aaaa".to_string()), - roles: vec1, - user: u, - } -} - -fn gen() -> Guild { - let u = gen_user(); - let m = gen_member(); - - let hm1 = HashMap::new(); - let hm2 = HashMap::new(); - let vec1 = Vec::new(); - let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600) - .ymd(2016, 11, 08) - .and_hms(0, 0, 0); - let mut hm3 = HashMap::new(); - let hm4 = HashMap::new(); - let hm5 = HashMap::new(); - let hm6 = HashMap::new(); - - hm3.insert(u.id, m); - - Guild { - afk_channel_id: Some(ChannelId(0)), - afk_timeout: 0, - channels: hm1, - default_message_notifications: DefaultMessageNotificationLevel::All, - emojis: hm2, - features: vec1, - icon: Some("/avatars/210/a_aaa.webp?size=1024".to_string()), - id: GuildId(1), - joined_at: dt, - large: false, - member_count: 1, - members: hm3, - mfa_level: MfaLevel::Elevated, - name: "Spaghetti".to_string(), - owner_id: UserId(210), - presences: hm4, - region: "NA".to_string(), - roles: hm5, - splash: Some("asdf".to_string()), - verification_level: VerificationLevel::None, - voice_states: hm6, - application_id: Some(ApplicationId(0)), - explicit_content_filter: ExplicitContentFilter::None, - system_channel_id: Some(ChannelId(0)), - } -} - - -#[test] -fn member_named_username() { - let guild = gen(); - let lhs = guild - .member_named("test#1432") - .unwrap() - .display_name(); - - assert_eq!(lhs, gen_member().display_name()); -} - -#[test] -fn member_named_nickname() { - let guild = gen(); - let lhs = guild.member_named("aaaa").unwrap().display_name(); - - assert_eq!(lhs, gen_member().display_name()); -} diff --git a/tests/test_http.rs b/tests/test_http.rs deleted file mode 100644 index 1389ef6..0000000 --- a/tests/test_http.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![cfg(feature = "http")] - -extern crate serenity; - -use serenity::http::AttachmentType; -use std::path::Path; - -#[test] -fn test_attachment_type() { - assert!(match AttachmentType::from(Path::new("./dogs/corgis/kona.png")) { - AttachmentType::Path(_) => true, - _ => false, - }); - assert!(match AttachmentType::from("./cats/copycat.png") { - AttachmentType::Path(_) => true, - _ => false, - }); -} diff --git a/tests/test_message.rs b/tests/test_message.rs deleted file mode 100644 index 71afcf5..0000000 --- a/tests/test_message.rs +++ /dev/null @@ -1,29 +0,0 @@ -extern crate serde; -extern crate serde_json; -extern crate serenity; - -use serde::de::Deserialize; -use serde_json::Value; -use serenity::model::channel::Message; -use std::fs::File; - -macro_rules! p { - ($s:ident, $filename:expr) => ({ - let f = File::open(concat!("./tests/resources/", $filename, ".json")).unwrap(); - let v = serde_json::from_reader::<File, Value>(f).unwrap(); - - $s::deserialize(v).unwrap() - }) -} - -#[test] -fn test_footer_deser() { - let mut message = p!(Message, "message_footer_1"); - - assert_eq!( - message.embeds.remove(0).footer.unwrap().text, - "2005-09-26 - 2013-09-26" - ); - - p!(Message, "message_footer_2"); -} diff --git a/tests/test_msg_builder.rs b/tests/test_msg_builder.rs deleted file mode 100644 index c291bf5..0000000 --- a/tests/test_msg_builder.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![cfg(feature = "utils")] - -extern crate serenity; - -use serenity::utils::MessageBuilder; -use serenity::utils::ContentModifier::*; -use serenity::model::guild::Emoji; -use serenity::model::id::{EmojiId, UserId}; - -#[test] -fn code_blocks() { - let content = MessageBuilder::new() - .push_codeblock("test", Some("rb")) - .build(); - assert_eq!(content, "```rb\ntest\n```"); -} - -#[test] -fn safe_content() { - let content = MessageBuilder::new() - .push_safe("@everyone discord.gg/discord-api") - .build(); - assert_ne!(content, "@everyone discord.gg/discord-api"); -} - -#[test] -fn no_free_formatting() { - let content = MessageBuilder::new().push_bold_safe("test**test").build(); - assert_ne!(content, "**test**test**"); -} - -#[test] -fn mentions() { - let content_emoji = MessageBuilder::new() - .emoji(&Emoji { - animated: false, - id: EmojiId(32), - name: "Rohrkatze".to_string(), - managed: false, - require_colons: true, - roles: vec![], - }) - .build(); - let content_mentions = MessageBuilder::new() - .channel(1) - .mention(&UserId(2)) - .role(3) - .user(4) - .build(); - assert_eq!(content_mentions, "<#1><@2><@&3><@4>"); - assert_eq!(content_emoji, "<:Rohrkatze:32>"); -} - -#[test] -fn content() { - let content = Bold + Italic + Code + "Fun!"; - - assert_eq!(content.to_string(), "***`Fun!`***"); -} - -#[test] -fn message_content() { - let message_content = MessageBuilder::new() - .push(Bold + Italic + Code + "Fun!") - .build(); - - assert_eq!(message_content, "***`Fun!`***"); -} - -#[test] -fn message_content_safe() { - let message_content = MessageBuilder::new() - .push_safe(Bold + Italic + "test**test") - .build(); - - assert_eq!(message_content, "***test\\*\\*test***"); -} diff --git a/tests/test_parsers.rs b/tests/test_parsers.rs deleted file mode 100644 index eddad53..0000000 --- a/tests/test_parsers.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![cfg(feature = "utils")] - -extern crate serenity; - -use serenity::utils::*; - -#[test] -fn invite_parser() { - assert_eq!(parse_invite("https://discord.gg/abc"), "abc"); - assert_eq!(parse_invite("http://discord.gg/abc"), "abc"); - assert_eq!(parse_invite("discord.gg/abc"), "abc"); -} - -#[test] -fn username_parser() { - assert_eq!(parse_username("<@12345>").unwrap(), 12_345); - assert_eq!(parse_username("<@!12345>").unwrap(), 12_345); -} - -#[test] -fn role_parser() { - assert_eq!(parse_role("<@&12345>").unwrap(), 12_345); -} - -#[test] -fn channel_parser() { - assert_eq!(parse_channel("<#12345>").unwrap(), 12_345); -} - -#[test] -fn emoji_parser() { - let emoji = parse_emoji("<:name:12345>").unwrap(); - assert_eq!(emoji.name, "name"); - assert_eq!(emoji.id, 12_345); -} - -#[test] -fn quote_parser() { - let parsed = parse_quotes("a \"b c\" d\"e f\" g"); - assert_eq!(parsed, ["a", "b c", "d", "e f", "g"]); -} diff --git a/tests/test_user.rs b/tests/test_user.rs deleted file mode 100644 index 267b578..0000000 --- a/tests/test_user.rs +++ /dev/null @@ -1,66 +0,0 @@ -extern crate serenity; - -#[cfg(feature = "model")] -mod model { - use serenity::model::id::UserId; - use serenity::model::user::User; - - fn gen() -> User { - User { - id: UserId(210), - avatar: Some("abc".to_string()), - bot: true, - discriminator: 1432, - name: "test".to_string(), - } - } - - #[test] - fn test_core() { - let mut user = gen(); - - assert!( - user.avatar_url() - .unwrap() - .ends_with("/avatars/210/abc.webp?size=1024",) - ); - assert!( - user.static_avatar_url() - .unwrap() - .ends_with("/avatars/210/abc.webp?size=1024",) - ); - - user.avatar = Some("a_aaa".to_string()); - assert!( - user.avatar_url() - .unwrap() - .ends_with("/avatars/210/a_aaa.gif?size=1024",) - ); - assert!( - user.static_avatar_url() - .unwrap() - .ends_with("/avatars/210/a_aaa.webp?size=1024",) - ); - - user.avatar = None; - assert!(user.avatar_url().is_none()); - - assert_eq!(user.tag(), "test#1432"); - } - - #[test] - fn default_avatars() { - let mut user = gen(); - - user.discriminator = 0; - assert!(user.default_avatar_url().ends_with("0.png")); - user.discriminator = 1; - assert!(user.default_avatar_url().ends_with("1.png")); - user.discriminator = 2; - assert!(user.default_avatar_url().ends_with("2.png")); - user.discriminator = 3; - assert!(user.default_avatar_url().ends_with("3.png")); - user.discriminator = 4; - assert!(user.default_avatar_url().ends_with("4.png")); - } -} diff --git a/tests/test_utils.rs b/tests/test_utils.rs deleted file mode 100644 index d5dac6d..0000000 --- a/tests/test_utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![cfg(feature = "utils")]
-
-extern crate serenity;
-
-use serenity::utils::*;
-
-#[test]
-fn test_is_nsfw() {
- assert!(!is_nsfw("general"));
- assert!(is_nsfw("nsfw"));
- assert!(is_nsfw("nsfw-test"));
- assert!(!is_nsfw("nsfw-"));
- assert!(!is_nsfw("général"));
- assert!(is_nsfw("nsfw-général"));
-}
|