mod builder;
pub use self::builder::ClientBuilder;
use crate::{
api_error::ApiError,
error::{Error, ErrorType},
ratelimiting::{RatelimitHeaders, Ratelimiter},
request::{
application::{
CreateFollowupMessage, CreateGlobalCommand, CreateGuildCommand, DeleteFollowupMessage,
DeleteGlobalCommand, DeleteGuildCommand, DeleteOriginalResponse, GetCommandPermissions,
GetGlobalCommands, GetGuildCommandPermissions, GetGuildCommands, InteractionCallback,
InteractionError, InteractionErrorType, SetCommandPermissions, SetGlobalCommands,
SetGuildCommands, UpdateCommandPermissions, UpdateFollowupMessage, UpdateGlobalCommand,
UpdateGuildCommand, UpdateOriginalResponse,
},
channel::stage::create_stage_instance::CreateStageInstanceError,
guild::{
create_guild::CreateGuildError, create_guild_channel::CreateGuildChannelError,
update_guild_channel_positions::Position,
},
prelude::*,
GetUserApplicationInfo, Method, Request,
},
API_VERSION,
};
use hyper::body::Bytes;
use hyper::{
body::{self, Buf},
client::{Client as HyperClient, HttpConnector},
header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT},
Body, Response, StatusCode,
};
use serde::de::DeserializeOwned;
use std::{
convert::TryFrom,
fmt::{Debug, Formatter, Result as FmtResult},
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
},
time::Duration,
};
use tokio::time;
use twilight_model::{
application::{
callback::InteractionResponse,
command::{permissions::CommandPermissions, Command},
},
channel::message::allowed_mentions::AllowedMentions,
guild::Permissions,
id::{
ApplicationId, ChannelId, CommandId, EmojiId, GuildId, IntegrationId, InteractionId,
MessageId, RoleId, UserId, WebhookId,
},
};
#[cfg(feature = "hyper-rustls")]
type HttpsConnector<T> = hyper_rustls::HttpsConnector<T>;
#[cfg(all(feature = "hyper-tls", not(feature = "hyper-rustls")))]
type HttpsConnector<T> = hyper_tls::HttpsConnector<T>;
struct State {
http: HyperClient<HttpsConnector<HttpConnector>, Body>,
default_headers: Option<HeaderMap>,
proxy: Option<Box<str>>,
ratelimiter: Option<Ratelimiter>,
timeout: Duration,
token_invalid: AtomicBool,
token: Option<Box<str>>,
use_http: bool,
pub(crate) application_id: AtomicU64,
pub(crate) default_allowed_mentions: Option<AllowedMentions>,
}
impl Debug for State {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("State")
.field("http", &self.http)
.field("default_headers", &self.default_headers)
.field("proxy", &self.proxy)
.field("ratelimiter", &self.ratelimiter)
.field("token", &self.token)
.field("use_http", &self.use_http)
.finish()
}
}
/// Twilight's http client.
///
/// Almost all of the client methods require authentication, and as such, the client must be
/// supplied with a Discord Token. Get yours [here].
///
/// # OAuth
///
/// To use Bearer tokens prefix the token with `"Bearer "`, including the space
/// at the end like so:
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use std::env;
/// use twilight_http::Client;
///
/// let bearer = env::var("BEARER_TOKEN")?;
/// let token = format!("Bearer {}", bearer);
///
/// let client = Client::new(token);
/// # Ok(()) }
/// ```
///
/// # Cloning
///
/// The client internally wraps its data within an Arc. This means that the
/// client can be cloned and passed around tasks and threads cheaply.
///
/// # Unauthorized behavior
///
/// When the client encounters an Unauthorized response it will take note that
/// the configured token is invalid. This may occur when the token has been
/// revoked or expired. When this happens, you must create a new client with the
/// new token. The client will no longer execute requests in order to
/// prevent API bans and will always return [`ErrorType::Unauthorized`].
///
/// # Examples
///
/// Create a client called `client`:
/// ```rust,no_run
/// use twilight_http::Client;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = Client::new("my token");
/// # Ok(()) }
/// ```
///
/// Use [`ClientBuilder`] to create a client called `client`, with a shorter timeout:
/// ```rust,no_run
/// use twilight_http::Client;
/// use std::time::Duration;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = Client::builder()
/// .token("my token")
/// .timeout(Duration::from_secs(5))
/// .build();
/// # Ok(()) }
/// ```
///
/// All the examples on this page assume you have already created a client, and have named it
/// `client`.
///
/// [here]: https://discord.com/developers/applications
#[derive(Clone, Debug)]
pub struct Client {
state: Arc<State>,
}
impl Client {
/// Create a new `hyper-rustls` or `hyper-tls` backed client with a token.
#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
pub fn new(token: impl Into<String>) -> Self {
ClientBuilder::default().token(token).build()
}
/// Create a new builder to create a client.
///
/// Refer to its documentation for more information.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
/// Retrieve an immutable reference to the token used by the client.
///
/// If the initial token provided is not prefixed with `Bot `, it will be, and this method
/// reflects that.
pub fn token(&self) -> Option<&str> {
self.state.token.as_deref()
}
/// Retrieve the [`ApplicationId`] used by interaction methods.
pub fn application_id(&self) -> Option<ApplicationId> {
let id = self.state.application_id.load(Ordering::Relaxed);
if id != 0 {
return Some(ApplicationId(id));
}
None
}
/// Set a new [`ApplicationId`] after building the client.
///
/// Returns the previous ID, if there was one.
pub fn set_application_id(&self, application_id: ApplicationId) -> Option<ApplicationId> {
let prev = self
.state
.application_id
.swap(application_id.0, Ordering::Relaxed);
if prev != 0 {
return Some(ApplicationId(prev));
}
None
}
/// Get the default [`AllowedMentions`] for sent messages.
pub fn default_allowed_mentions(&self) -> Option<AllowedMentions> {
self.state.default_allowed_mentions.clone()
}
/// Get the Ratelimiter used by the client internally.
///
/// This will return `None` only if ratelimit handling
/// has been explicitly disabled in the [`ClientBuilder`].
pub fn ratelimiter(&self) -> Option<Ratelimiter> {
self.state.ratelimiter.clone()
}
/// Get the audit log for a guild.
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::GuildId;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("token");
/// let guild_id = GuildId(101);
/// let audit_log = client
/// // not done
/// .audit_log(guild_id)
/// .await?;
/// # Ok(()) }
/// ```
pub fn audit_log(&self, guild_id: GuildId) -> GetAuditLog<'_> {
GetAuditLog::new(self, guild_id)
}
/// Retrieve the bans for a guild.
///
/// # Examples
///
/// Retrieve the bans for guild `1`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::GuildId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(1);
///
/// let bans = client.bans(guild_id).await?;
/// # Ok(()) }
/// ```
pub fn bans(&self, guild_id: GuildId) -> GetBans<'_> {
GetBans::new(self, guild_id)
}
/// Get information about a ban of a guild.
///
/// Includes the user banned and the reason.
pub fn ban(&self, guild_id: GuildId, user_id: UserId) -> GetBan<'_> {
GetBan::new(self, guild_id, user_id)
}
/// Bans a user from a guild, optionally with the number of days' worth of
/// messages to delete and the reason.
///
/// # Examples
///
/// Ban user `200` from guild `100`, deleting
/// 1 day's worth of messages, for the reason `"memes"`:
///
/// ```rust,no_run
/// # use twilight_http::{request::AuditLogReason, Client};
/// use twilight_model::id::{GuildId, UserId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(100);
/// let user_id = UserId(200);
/// client.create_ban(guild_id, user_id)
/// .delete_message_days(1)?
/// .reason("memes")?
/// .await?;
/// # Ok(()) }
/// ```
pub fn create_ban(&self, guild_id: GuildId, user_id: UserId) -> CreateBan<'_> {
CreateBan::new(self, guild_id, user_id)
}
/// Remove a ban from a user in a guild.
///
/// # Examples
///
/// Unban user `200` from guild `100`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::{GuildId, UserId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(100);
/// let user_id = UserId(200);
///
/// client.delete_ban(guild_id, user_id).await?;
/// # Ok(()) }
/// ```
pub fn delete_ban(&self, guild_id: GuildId, user_id: UserId) -> DeleteBan<'_> {
DeleteBan::new(self, guild_id, user_id)
}
/// Get a channel by its ID.
///
/// # Examples
///
/// Get channel `100`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::ChannelId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let channel_id = ChannelId(100);
/// #
/// let channel = client.channel(channel_id).await?;
/// # Ok(()) }
/// ```
pub fn channel(&self, channel_id: ChannelId) -> GetChannel<'_> {
GetChannel::new(self, channel_id)
}
/// Delete a channel by ID.
pub fn delete_channel(&self, channel_id: ChannelId) -> DeleteChannel<'_> {
DeleteChannel::new(self, channel_id)
}
/// Update a channel.
///
/// All fields are optional. The minimum length of the name is 2 UTF-16 characters and the
/// maximum is 100 UTF-16 characters.
pub fn update_channel(&self, channel_id: ChannelId) -> UpdateChannel<'_> {
UpdateChannel::new(self, channel_id)
}
/// Follows a news channel by [`ChannelId`].
///
/// The type returned is [`FollowedChannel`].
///
/// [`FollowedChannel`]: ::twilight_model::channel::FollowedChannel
pub fn follow_news_channel(
&self,
channel_id: ChannelId,
webhook_channel_id: ChannelId,
) -> FollowNewsChannel<'_> {
FollowNewsChannel::new(self, channel_id, webhook_channel_id)
}
/// Get the invites for a guild channel.
///
/// Requires the [`MANAGE_CHANNELS`] permission. This method only works if
/// the channel is of type [`GuildChannel`].
///
/// [`MANAGE_CHANNELS`]: twilight_model::guild::Permissions::MANAGE_CHANNELS
/// [`GuildChannel`]: twilight_model::channel::GuildChannel
pub fn channel_invites(&self, channel_id: ChannelId) -> GetChannelInvites<'_> {
GetChannelInvites::new(self, channel_id)
}
/// Get channel messages, by [`ChannelId`].
///
/// Only one of [`after`], [`around`], and [`before`] can be specified at a time.
/// Once these are specified, the type returned is [`GetChannelMessagesConfigured`].
///
/// If [`limit`] is unspecified, the default set by Discord is 50.
///
/// # Examples
///
/// ```rust,no_run
/// use twilight_http::Client;
/// use twilight_model::id::{ChannelId, MessageId};
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = Client::new("my token");
/// let channel_id = ChannelId(123);
/// let message_id = MessageId(234);
/// let limit: u64 = 6;
///
/// let messages = client
/// .channel_messages(channel_id)
/// .before(message_id)
/// .limit(limit)?
/// .await?;
///
/// # Ok(()) }
/// ```
///
/// # Errors
///
/// Returns a [`GetChannelMessagesErrorType::LimitInvalid`] error type if
/// the amount is less than 1 or greater than 100.
///
/// [`after`]: GetChannelMessages::after
/// [`around`]: GetChannelMessages::around
/// [`before`]: GetChannelMessages::before
/// [`GetChannelMessagesConfigured`]: crate::request::channel::message::GetChannelMessagesConfigured
/// [`limit`]: GetChannelMessages::limit
/// [`GetChannelMessagesErrorType::LimitInvalid`]: crate::request::channel::message::get_channel_messages::GetChannelMessagesErrorType::LimitInvalid
pub fn channel_messages(&self, channel_id: ChannelId) -> GetChannelMessages<'_> {
GetChannelMessages::new(self, channel_id)
}
pub const fn delete_channel_permission(
&self,
channel_id: ChannelId,
) -> DeleteChannelPermission<'_> {
DeleteChannelPermission::new(self, channel_id)
}
/// Update the permissions for a role or a user in a channel.
///
/// # Examples:
///
/// Create permission overrides for a role to view the channel, but not send messages:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::guild::Permissions;
/// use twilight_model::id::{ChannelId, RoleId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
///
/// let channel_id = ChannelId(123);
/// let allow = Permissions::VIEW_CHANNEL;
/// let deny = Permissions::SEND_MESSAGES;
/// let role_id = RoleId(432);
///
/// client.update_channel_permission(channel_id, allow, deny)
/// .role(role_id)
/// .await?;
/// # Ok(()) }
/// ```
pub const fn update_channel_permission(
&self,
channel_id: ChannelId,
allow: Permissions,
deny: Permissions,
) -> UpdateChannelPermission<'_> {
UpdateChannelPermission::new(self, channel_id, allow, deny)
}
/// Get all the webhooks of a channel.
pub fn channel_webhooks(&self, channel_id: ChannelId) -> GetChannelWebhooks<'_> {
GetChannelWebhooks::new(self, channel_id)
}
/// Get information about the current user.
pub fn current_user(&self) -> GetCurrentUser<'_> {
GetCurrentUser::new(self)
}
/// Get information about the current bot application.
pub fn current_user_application(&self) -> GetUserApplicationInfo<'_> {
GetUserApplicationInfo::new(self)
}
/// Update the current user.
///
/// All paramaters are optional. If the username is changed, it may cause the discriminator to
/// be randomized.
pub fn update_current_user(&self) -> UpdateCurrentUser<'_> {
UpdateCurrentUser::new(self)
}
/// Update the current user's voice state.
///
/// All paramaters are optional.
///
/// # Caveats
///
/// - `channel_id` must currently point to a stage channel.
/// - Current user must have already joined `channel_id`.
pub fn update_current_user_voice_state(
&self,
guild_id: GuildId,
channel_id: ChannelId,
) -> UpdateCurrentUserVoiceState<'_> {
UpdateCurrentUserVoiceState::new(self, guild_id, channel_id)
}
/// Get the current user's connections.
///
/// Requires the `connections` `OAuth2` scope.
pub fn current_user_connections(&self) -> GetCurrentUserConnections<'_> {
GetCurrentUserConnections::new(self)
}
/// Returns a list of guilds for the current user.
///
/// # Examples
///
/// Get the first 25 guilds with an ID after `300` and before
/// `400`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::GuildId;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let after = GuildId(300);
/// let before = GuildId(400);
/// let guilds = client.current_user_guilds()
/// .after(after)
/// .before(before)
/// .limit(25)?
/// .await?;
/// # Ok(()) }
/// ```
pub fn current_user_guilds(&self) -> GetCurrentUserGuilds<'_> {
GetCurrentUserGuilds::new(self)
}
/// Changes the user's nickname in a guild.
pub fn update_current_user_nick(
&self,
guild_id: GuildId,
nick: impl Into<String>,
) -> UpdateCurrentUserNick<'_> {
UpdateCurrentUserNick::new(self, guild_id, nick)
}
/// Get the emojis for a guild, by the guild's id.
///
/// # Examples
///
/// Get the emojis for guild `100`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::GuildId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(100);
///
/// client.emojis(guild_id).await?;
/// # Ok(()) }
/// ```
pub fn emojis(&self, guild_id: GuildId) -> GetEmojis<'_> {
GetEmojis::new(self, guild_id)
}
/// Get an emoji for a guild by the the guild's ID and emoji's ID.
///
/// # Examples
///
/// Get emoji `100` from guild `50`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::{EmojiId, GuildId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(50);
/// let emoji_id = EmojiId(100);
///
/// client.emoji(guild_id, emoji_id).await?;
/// # Ok(()) }
/// ```
pub fn emoji(&self, guild_id: GuildId, emoji_id: EmojiId) -> GetEmoji<'_> {
GetEmoji::new(self, guild_id, emoji_id)
}
/// Create an emoji in a guild.
///
/// The emoji must be a Data URI, in the form of `data:image/{type};base64,{data}` where
/// `{type}` is the image MIME type and `{data}` is the base64-encoded image. Refer to [the
/// discord docs] for more information about image data.
///
/// [the discord docs]: https://discord.com/developers/docs/reference#image-data
pub fn create_emoji(
&self,
guild_id: GuildId,
name: impl Into<String>,
image: impl Into<String>,
) -> CreateEmoji<'_> {
CreateEmoji::new(self, guild_id, name, image)
}
/// Delete an emoji in a guild, by id.
pub fn delete_emoji(&self, guild_id: GuildId, emoji_id: EmojiId) -> DeleteEmoji<'_> {
DeleteEmoji::new(self, guild_id, emoji_id)
}
/// Update an emoji in a guild, by id.
pub fn update_emoji(&self, guild_id: GuildId, emoji_id: EmojiId) -> UpdateEmoji<'_> {
UpdateEmoji::new(self, guild_id, emoji_id)
}
/// Get information about the gateway, optionally with additional information detailing the
/// number of shards to use and sessions remaining.
///
/// # Examples
///
/// Get the gateway connection URL without bot information:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let info = client.gateway().await?;
/// # Ok(()) }
/// ```
///
/// Get the gateway connection URL with additional shard and session information, which
/// requires specifying a bot token:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let info = client.gateway().authed().await?;
///
/// println!("URL: {}", info.url);
/// println!("Recommended shards to use: {}", info.shards);
/// # Ok(()) }
/// ```
pub fn gateway(&self) -> GetGateway<'_> {
GetGateway::new(self)
}
/// Get information about a guild.
pub fn guild(&self, guild_id: GuildId) -> GetGuild<'_> {
GetGuild::new(self, guild_id)
}
/// Create a new request to create a guild.
///
/// The minimum length of the name is 2 UTF-16 characters and the maximum is 100 UTF-16
/// characters. This endpoint can only be used by bots in less than 10 guilds.
///
/// # Errors
///
/// Returns a [`CreateGuildErrorType::NameInvalid`] error type if the name
/// length is too short or too long.
///
/// [`CreateGuildErrorType::NameInvalid`]: crate::request::guild::create_guild::CreateGuildErrorType::NameInvalid
pub fn create_guild(
&self,
name: impl Into<String>,
) -> Result<CreateGuild<'_>, CreateGuildError> {
CreateGuild::new(self, name)
}
/// Delete a guild permanently. The user must be the owner.
pub fn delete_guild(&self, guild_id: GuildId) -> DeleteGuild<'_> {
DeleteGuild::new(self, guild_id)
}
/// Update a guild.
///
/// All endpoints are optional. Refer to [the discord docs] for more information.
///
/// [the discord docs]: https://discord.com/developers/docs/resources/guild#modify-guild
pub fn update_guild(&self, guild_id: GuildId) -> UpdateGuild<'_> {
UpdateGuild::new(self, guild_id)
}
/// Leave a guild by id.
pub fn leave_guild(&self, guild_id: GuildId) -> LeaveGuild<'_> {
LeaveGuild::new(self, guild_id)
}
/// Get the channels in a guild.
pub fn guild_channels(&self, guild_id: GuildId) -> GetGuildChannels<'_> {
GetGuildChannels::new(self, guild_id)
}
/// Create a new request to create a guild channel.
///
/// All fields are optional except for name. The minimum length of the name is 2 UTF-16
/// characters and the maximum is 100 UTF-16 characters.
///
/// # Errors
///
/// Returns a [`CreateGuildChannelErrorType::NameInvalid`] error type when
/// the length of the name is either fewer than 2 UTF-16 characters or more than 100 UTF-16 characters.
///
/// Returns a [`CreateGuildChannelErrorType::RateLimitPerUserInvalid`] error
/// type when the seconds of the rate limit per user is more than 21600.
///
/// Returns a [`CreateGuildChannelErrorType::TopicInvalid`] error type when
/// the length of the topic is more than 1024 UTF-16 characters.
///
/// [`CreateGuildChannelErrorType::NameInvalid`]: crate::request::guild::create_guild_channel::CreateGuildChannelErrorType::NameInvalid
/// [`CreateGuildChannelErrorType::RateLimitPerUserInvalid`]: crate::request::guild::create_guild_channel::CreateGuildChannelErrorType::RateLimitPerUserInvalid
/// [`CreateGuildChannelErrorType::TopicInvalid`]: crate::request::guild::create_guild_channel::CreateGuildChannelErrorType::TopicInvalid
pub fn create_guild_channel(
&self,
guild_id: GuildId,
name: impl Into<String>,
) -> Result<CreateGuildChannel<'_>, CreateGuildChannelError> {
CreateGuildChannel::new(self, guild_id, name)
}
/// Modify the positions of the channels.
///
/// The minimum amount of channels to modify, is a swap between two channels.
///
/// This function accepts an `Iterator` of `(ChannelId, u64)`. It also
/// accepts an `Iterator` of `Position`, which has extra fields.
pub fn update_guild_channel_positions(
&self,
guild_id: GuildId,
channel_positions: impl Iterator<Item = impl Into<Position>>,
) -> UpdateGuildChannelPositions<'_> {
UpdateGuildChannelPositions::new(self, guild_id, channel_positions)
}
/// Get the guild widget.
///
/// Refer to [the discord docs] for more information.
///
/// [the discord docs]: https://discord.com/developers/docs/resources/guild#get-guild-widget
pub fn guild_widget(&self, guild_id: GuildId) -> GetGuildWidget<'_> {
GetGuildWidget::new(self, guild_id)
}
/// Modify the guild widget.
pub fn update_guild_widget(&self, guild_id: GuildId) -> UpdateGuildWidget<'_> {
UpdateGuildWidget::new(self, guild_id)
}
/// Get the guild's integrations.
pub fn guild_integrations(&self, guild_id: GuildId) -> GetGuildIntegrations<'_> {
GetGuildIntegrations::new(self, guild_id)
}
/// Delete an integration for a guild, by the integration's id.
pub fn delete_guild_integration(
&self,
guild_id: GuildId,
integration_id: IntegrationId,
) -> DeleteGuildIntegration<'_> {
DeleteGuildIntegration::new(self, guild_id, integration_id)
}
/// Get information about the invites of a guild.
///
/// Requires the [`MANAGE_GUILD`] permission.
///
/// [`MANAGE_GUILD`]: twilight_model::guild::Permissions::MANAGE_GUILD
pub fn guild_invites(&self, guild_id: GuildId) -> GetGuildInvites<'_> {
GetGuildInvites::new(self, guild_id)
}
/// Get the members of a guild, by id.
///
/// The upper limit to this request is 1000. If more than 1000 members are needed, the requests
/// must be chained. Discord defaults the limit to 1.
///
/// # Examples
///
/// Get the first 500 members of guild `100` after user ID `3000`:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::{GuildId, UserId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(100);
/// let user_id = UserId(3000);
/// let members = client.guild_members(guild_id).after(user_id).await?;
/// # Ok(()) }
/// ```
///
/// # Errors
///
/// Returns a [`GetGuildMembersErrorType::LimitInvalid`] error type if the
/// limit is invalid.
///
/// [`GetGuildMembersErrorType::LimitInvalid`]: crate::request::guild::member::get_guild_members::GetGuildMembersErrorType::LimitInvalid
pub fn guild_members(&self, guild_id: GuildId) -> GetGuildMembers<'_> {
GetGuildMembers::new(self, guild_id)
}
/// Search the members of a specific guild by a query.
///
/// The upper limit to this request is 1000. Discord defaults the limit to 1.
///
/// # Examples
///
/// Get the first 10 members of guild `100` matching `Wumpus`:
///
/// ```rust,no_run
/// use twilight_http::Client;
/// use twilight_model::id::GuildId;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = Client::new("my token");
///
/// let guild_id = GuildId(100);
/// let members = client.search_guild_members(guild_id, String::from("Wumpus")).limit(10)?.await?;
/// # Ok(()) }
/// ```
///
/// # Errors
///
/// Returns a [`SearchGuildMembersErrorType::LimitInvalid`] error type if
/// the limit is invalid.
///
/// [`GUILD_MEMBERS`]: ../../twilight_model/gateway/struct.Intents.html#associatedconstant.GUILD_MEMBERS
/// [`SearchGuildMembersErrorType::LimitInvalid`]: ../request/guild/member/search_guild_members/enum.SearchGuildMembersError.html#variant.LimitInvalid
pub fn search_guild_members(
&self,
guild_id: GuildId,
query: impl Into<String>,
) -> SearchGuildMembers<'_> {
SearchGuildMembers::new(self, guild_id, query)
}
/// Get a member of a guild, by their id.
pub fn guild_member(&self, guild_id: GuildId, user_id: UserId) -> GetMember<'_> {
GetMember::new(self, guild_id, user_id)
}
/// Add a user to a guild.
///
/// An access token for the user with `guilds.join` scope is required. All
/// other fields are optional. Refer to [the discord docs] for more
/// information.
///
/// # Errors
///
/// Returns [`AddGuildMemberErrorType::NicknameInvalid`] if the nickname is
/// too short or too long.
///
/// [`AddGuildMemberErrorType::NickNameInvalid`]: crate::request::guild::member::add_guild_member::AddGuildMemberErrorType::NicknameInvalid
///
/// [the discord docs]: https://discord.com/developers/docs/resources/guild#add-guild-member
pub fn add_guild_member(
&self,
guild_id: GuildId,
user_id: UserId,
access_token: impl Into<String>,
) -> AddGuildMember<'_> {
AddGuildMember::new(self, guild_id, user_id, access_token)
}
/// Kick a member from a guild.
pub fn remove_guild_member(&self, guild_id: GuildId, user_id: UserId) -> RemoveMember<'_> {
RemoveMember::new(self, guild_id, user_id)
}
/// Update a guild member.
///
/// All fields are optional. Refer to [the discord docs] for more information.
///
/// # Examples
///
/// Update a member's nickname to "pinky pie" and server mute them:
///
/// ```rust,no_run
/// use std::env;
/// use twilight_http::Client;
/// use twilight_model::id::{GuildId, UserId};
///
/// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new(env::var("DISCORD_TOKEN")?);
/// let member = client.update_guild_member(GuildId(1), UserId(2))
/// .mute(true)
/// .nick(Some("pinkie pie".to_owned()))?
/// .await?;
///
/// println!("user {} now has the nickname '{:?}'", member.user.id, member.nick);
/// # Ok(()) }
/// ```
///
/// # Errors
///
/// Returns [`UpdateGuildMemberErrorType::NicknameInvalid`] if the nickname length is too short or too
/// long.
///
/// [`UpdateGuildMemberErrorType::NicknameInvalid`]: crate::request::guild::member::update_guild_member::UpdateGuildMemberErrorType::NicknameInvalid
///
/// [the discord docs]: https://discord.com/developers/docs/resources/guild#modify-guild-member
pub fn update_guild_member(&self, guild_id: GuildId, user_id: UserId) -> UpdateGuildMember<'_> {
UpdateGuildMember::new(self, guild_id, user_id)
}
/// Add a role to a member in a guild.
///
/// # Examples
///
/// In guild `1`, add role `2` to user `3`, for the reason `"test"`:
///
/// ```rust,no_run
/// # use twilight_http::{request::AuditLogReason, Client};
/// use twilight_model::id::{GuildId, RoleId, UserId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let guild_id = GuildId(1);
/// let role_id = RoleId(2);
/// let user_id = UserId(3);
///
/// client.add_guild_member_role(guild_id, user_id, role_id).reason("test")?.await?;
/// # Ok(()) }
/// ```
pub fn add_guild_member_role(
&self,
guild_id: GuildId,
user_id: UserId,
role_id: RoleId,
) -> AddRoleToMember<'_> {
AddRoleToMember::new(self, guild_id, user_id, role_id)
}
/// Remove a role from a member in a guild, by id.
pub fn remove_guild_member_role(
&self,
guild_id: GuildId,
user_id: UserId,
role_id: RoleId,
) -> RemoveRoleFromMember<'_> {
RemoveRoleFromMember::new(self, guild_id, user_id, role_id)
}
/// For public guilds, get the guild preview.
///
/// This works even if the user is not in the guild.
pub fn guild_preview(&self, guild_id: GuildId) -> GetGuildPreview<'_> {
GetGuildPreview::new(self, guild_id)
}
/// Get the counts of guild members to be pruned.
pub fn guild_prune_count(&self, guild_id: GuildId) -> GetGuildPruneCount<'_> {
GetGuildPruneCount::new(self, guild_id)
}
/// Begin a guild prune.
///
/// Refer to [the discord docs] for more information.
///
/// [the discord docs]: https://discord.com/developers/docs/resources/guild#begin-guild-prune
pub fn create_guild_prune(&self, guild_id: GuildId) -> CreateGuildPrune<'_> {
CreateGuildPrune::new(self, guild_id)
}
/// Get a guild's vanity url, if there is one.
pub fn guild_vanity_url(&self, guild_id: GuildId) -> GetGuildVanityUrl<'_> {
GetGuildVanityUrl::new(self, guild_id)
}
/// Get voice region data for the guild.
///
/// Can return VIP servers if the guild is VIP-enabled.
pub fn guild_voice_regions(&self, guild_id: GuildId) -> GetGuildVoiceRegions<'_> {
GetGuildVoiceRegions::new(self, guild_id)
}
/// Get the webhooks of a guild.
pub fn guild_webhooks(&self, guild_id: GuildId) -> GetGuildWebhooks<'_> {
GetGuildWebhooks::new(self, guild_id)
}
/// Get the guild's welcome screen.
pub fn guild_welcome_screen(&self, guild_id: GuildId) -> GetGuildWelcomeScreen<'_> {
GetGuildWelcomeScreen::new(self, guild_id)
}
/// Update the guild's welcome screen.
///
/// Requires the [`MANAGE_GUILD`] permission.
///
/// [`MANAGE_GUILD`]: twilight_model::guild::Permissions::MANAGE_GUILD
pub fn update_guild_welcome_screen(&self, guild_id: GuildId) -> UpdateGuildWelcomeScreen<'_> {
UpdateGuildWelcomeScreen::new(self, guild_id)
}
/// Get information about an invite by its code.
///
/// If [`with_counts`] is called, the returned invite will contain
/// approximate member counts. If [`with_expiration`] is called, it will
/// contain the expiration date.
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let invite = client
/// .invite("code")
/// .with_counts()
/// .await?;
/// # Ok(()) }
/// ```
///
/// [`with_counts`]: crate::request::channel::invite::GetInvite::with_counts
/// [`with_expiration`]: crate::request::channel::invite::GetInvite::with_expiration
pub fn invite(&self, code: impl Into<String>) -> GetInvite<'_> {
GetInvite::new(self, code)
}
/// Create an invite, with options.
///
/// Requires the [`CREATE_INVITE`] permission.
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::ChannelId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let channel_id = ChannelId(123);
/// let invite = client
/// .create_invite(channel_id)
/// .max_uses(3)?
/// .await?;
/// # Ok(()) }
/// ```
///
/// [`CREATE_INVITE`]: twilight_model::guild::Permissions::CREATE_INVITE
pub fn create_invite(&self, channel_id: ChannelId) -> CreateInvite<'_> {
CreateInvite::new(self, channel_id)
}
/// Delete an invite by its code.
///
/// Requires the [`MANAGE_CHANNELS`] permission on the channel this invite
/// belongs to, or [`MANAGE_GUILD`] to remove any invite across the guild.
///
/// [`MANAGE_CHANNELS`]: twilight_model::guild::Permissions::MANAGE_CHANNELS
/// [`MANAGE_GUILD`]: twilight_model::guild::Permissions::MANAGE_GUILD
pub fn delete_invite(&self, code: impl Into<String>) -> DeleteInvite<'_> {
DeleteInvite::new(self, code)
}
/// Get a message by [`ChannelId`] and [`MessageId`].
pub fn message(&self, channel_id: ChannelId, message_id: MessageId) -> GetMessage<'_> {
GetMessage::new(self, channel_id, message_id)
}
/// Send a message to a channel.
///
/// # Example
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::ChannelId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let channel_id = ChannelId(123);
/// let message = client
/// .create_message(channel_id)
/// .content("Twilight is best pony")?
/// .tts(true)
/// .await?;
/// # Ok(()) }
/// ```
///
/// # Errors
///
/// The method [`content`] returns
/// [`CreateMessageErrorType::ContentInvalid`] if the content is over 2000
/// UTF-16 characters.
///
/// The method [`embed`] returns
/// [`CreateMessageErrorType::EmbedTooLarge`] if the length of the embed
/// is over 6000 characters.
///
/// [`content`]: crate::request::channel::message::create_message::CreateMessage::content
/// [`embed`]: crate::request::channel::message::create_message::CreateMessage::embed
/// [`CreateMessageErrorType::ContentInvalid`]:
/// crate::request::channel::message::create_message::CreateMessageErrorType::ContentInvalid
/// [`CreateMessageErrorType::EmbedTooLarge`]:
/// crate::request::channel::message::create_message::CreateMessageErrorType::EmbedTooLarge
pub fn create_message(&self, channel_id: ChannelId) -> CreateMessage<'_> {
CreateMessage::new(self, channel_id)
}
/// Delete a message by [`ChannelId`] and [`MessageId`].
pub fn delete_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> DeleteMessage<'_> {
DeleteMessage::new(self, channel_id, message_id)
}
/// Delete messages by [`ChannelId`] and Vec<[`MessageId`]>.
///
/// The vec count can be between 2 and 100. If the supplied [`MessageId`]s are invalid, they
/// still count towards the lower and upper limits. This method will not delete messages older
/// than two weeks. Refer to [the discord docs] for more information.
///
/// [the discord docs]: https://discord.com/developers/docs/resources/channel#bulk-delete-messages
pub fn delete_messages(
&self,
channel_id: ChannelId,
message_ids: impl Into<Vec<MessageId>>,
) -> DeleteMessages<'_> {
DeleteMessages::new(self, channel_id, message_ids)
}
/// Update a message by [`ChannelId`] and [`MessageId`].
///
/// You can pass `None` to any of the methods to remove the associated field.
/// For example, if you have a message with an embed you want to remove, you can
/// use `.[embed](None)` to remove the embed.
///
/// # Examples
///
/// Replace the content with `"test update"`:
///
/// ```rust,no_run
/// use twilight_http::Client;
/// use twilight_model::id::{ChannelId, MessageId};
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = Client::new("my token");
/// client.update_message(ChannelId(1), MessageId(2))
/// .content("test update".to_owned())?
/// .await?;
/// # Ok(()) }
/// ```
///
/// Remove the message's content:
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::{ChannelId, MessageId};
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// client.update_message(ChannelId(1), MessageId(2))
/// .content(None)?
/// .await?;
/// # Ok(()) }
/// ```
///
/// [embed]: Self::embed
pub fn update_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> UpdateMessage<'_> {
UpdateMessage::new(self, channel_id, message_id)
}
/// Crosspost a message by [`ChannelId`] and [`MessageId`].
pub fn crosspost_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> CrosspostMessage<'_> {
CrosspostMessage::new(self, channel_id, message_id)
}
/// Get the pins of a channel.
pub fn pins(&self, channel_id: ChannelId) -> GetPins<'_> {
GetPins::new(self, channel_id)
}
/// Create a new pin in a channel, by ID.
pub fn create_pin(&self, channel_id: ChannelId, message_id: MessageId) -> CreatePin<'_> {
CreatePin::new(self, channel_id, message_id)
}
/// Delete a pin in a channel, by ID.
pub fn delete_pin(&self, channel_id: ChannelId, message_id: MessageId) -> DeletePin<'_> {
DeletePin::new(self, channel_id, message_id)
}
/// Get a list of users that reacted to a message with an `emoji`.
///
/// This endpoint is limited to 100 users maximum, so if a message has more than 100 reactions,
/// requests must be chained until all reactions are retireved.
pub fn reactions(
&self,
channel_id: ChannelId,
message_id: MessageId,
emoji: RequestReactionType,
) -> GetReactions<'_> {
GetReactions::new(self, channel_id, message_id, emoji)
}
/// Create a reaction in a [`ChannelId`] on a [`MessageId`].
///
/// The reaction must be a variant of [`RequestReactionType`].
///
/// # Examples
/// ```rust,no_run
/// # use twilight_http::{Client, request::channel::reaction::RequestReactionType};
/// # use twilight_model::{
/// # id::{ChannelId, MessageId},
/// # };
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// # let client = Client::new("my token");
/// #
/// let channel_id = ChannelId(123);
/// let message_id = MessageId(456);
/// let emoji = RequestReactionType::Unicode { name: String::from("🌃") };
///
/// let reaction = client
/// .create_reaction(channel_id, message_id, emoji)
/// .await?;
/// # Ok(()) }
/// ```
pub fn create_reaction(
&self,
channel_id: ChannelId,
message_id: MessageId,
emoji: RequestReactionType,
) -> CreateReaction<'_> {
CreateReaction::new(self, channel_id, message_id, emoji)
}
/// Delete the current user's (`@me`) reaction on a message.
pub fn delete_current_user_reaction(
&self,
channel_id: ChannelId,
message_id: MessageId,
emoji: RequestReactionType,
) -> DeleteReaction<'_> {
DeleteReaction::new(self, channel_id, message_id, emoji, "@me")
}
/// Delete a reaction by a user on a message.
pub fn delete_reaction(
&self,
channel_id: ChannelId,
message_id: MessageId,
emoji: RequestReactionType,
user_id: UserId,
) -> DeleteReaction<'_> {
DeleteReaction::new(self, channel_id, message_id, emoji, user_id.to_string())
}
/// Remove all reactions on a message of an emoji.
pub fn delete_all_reaction(
&self,
channel_id: ChannelId,
message_id: MessageId,
emoji: RequestReactionType,
) -> DeleteAllReaction<'_> {
DeleteAllReaction::new(self, channel_id, message_id, emoji)
}
/// Delete all reactions by all users on a message.
pub fn delete_all_reactions(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> DeleteAllReactions<'_> {
DeleteAllReactions::new(self, channel_id, message_id)
}
/// Fire a Typing Start event in the channel.
pub fn create_typing_trigger(&self, channel_id: ChannelId) -> CreateTypingTrigger<'_> {
CreateTypingTrigger::new(self, channel_id)
}
/// Create a group DM.
///
/// This endpoint is limited to 10 active group DMs.
pub fn create_private_channel(&self, recipient_id: UserId) -> CreatePrivateChannel<'_> {
CreatePrivateChannel::new(self, recipient_id)
}
/// Get the roles of a guild.
pub fn roles(&self, guild_id: GuildId) -> GetGuildRoles<'_> {
GetGuildRoles::new(self, guild_id)
}
/// Create a role in a guild.
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// use twilight_model::id::GuildId;
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("my token");
/// let guild_id = GuildId(234);
///
/// client.create_role(guild_id)
/// .color(0xd90083)
/// .name("Bright Pink")
/// .await?;
/// # Ok(()) }
/// ```
pub fn create_role(&self, guild_id: GuildId) -> CreateRole<'_> {
CreateRole::new(self, guild_id)
}
/// Delete a role in a guild, by id.
pub fn delete_role(&self, guild_id: GuildId, role_id: RoleId) -> DeleteRole<'_> {
DeleteRole::new(self, guild_id, role_id)
}
/// Update a role by guild id and its id.
pub fn update_role(&self, guild_id: GuildId, role_id: RoleId) -> UpdateRole<'_> {
UpdateRole::new(self, guild_id, role_id)
}
/// Modify the position of the roles.
///
/// The minimum amount of roles to modify, is a swap between two roles.
pub fn update_role_positions(
&self,
guild_id: GuildId,
roles: impl Iterator<Item = (RoleId, u64)>,
) -> UpdateRolePositions<'_> {
UpdateRolePositions::new(self, guild_id, roles)
}
/// Create a new stage instance associated with a stage channel.
///
/// Requires the user to be a moderator of the stage channel.
///
/// # Errors
///
/// Returns a [`CreateStageInstanceError`] of type [`InvalidTopic`] when the
/// topic is not between 1 and 120 characters in length.
///
/// [`InvalidTopic`]: crate::request::channel::stage::create_stage_instance::CreateStageInstanceErrorType::InvalidTopic
pub fn create_stage_instance(
&self,
channel_id: ChannelId,
topic: impl Into<String>,
) -> Result<CreateStageInstance<'_>, CreateStageInstanceError> {
CreateStageInstance::new(self, channel_id, topic)
}
/// Gets the stage instance associated with a stage channel, if it exists.
pub fn stage_instance(&self, channel_id: ChannelId) -> GetStageInstance<'_> {
GetStageInstance::new(self, channel_id)
}
/// Update fields of an existing stage instance.
///
/// Requires the user to be a moderator of the stage channel.
pub fn update_stage_instance(&self, channel_id: ChannelId) -> UpdateStageInstance<'_> {
UpdateStageInstance::new(self, channel_id)
}
/// Delete the stage instance of a stage channel.
///
/// Requires the user to be a moderator of the stage channel.
pub fn delete_stage_instance(&self, channel_id: ChannelId) -> DeleteStageInstance<'_> {
DeleteStageInstance::new(self, channel_id)
}
/// Create a new guild based on a template.
///
/// This endpoint can only be used by bots in less than 10 guilds.
///
/// # Errors
///
/// Returns a [`CreateGuildFromTemplateErrorType::NameInvalid`] error type
/// if the name is invalid.
///
/// [`CreateGuildFromTemplateErrorType::NameInvalid`]: crate::request::template::create_guild_from_template::CreateGuildFromTemplateErrorType::NameInvalid
pub fn create_guild_from_template(
&self,
template_code: impl Into<String>,
name: impl Into<String>,
) -> Result<CreateGuildFromTemplate<'_>, CreateGuildFromTemplateError> {
CreateGuildFromTemplate::new(self, template_code, name)
}
/// Create a template from the current state of the guild.
///
/// Requires the `MANAGE_GUILD` permission. The name must be at least 1 and
/// at most 100 characters in length.
///
/// # Errors
///
/// Returns a [`CreateTemplateErrorType::NameInvalid`] error type if the
/// name is invalid.
///
/// [`CreateTemplateErrorType::NameInvalid`]: crate::request::template::create_template::CreateTemplateErrorType::NameInvalid
pub fn create_template(
&self,
guild_id: GuildId,
name: impl Into<String>,
) -> Result<CreateTemplate<'_>, CreateTemplateError> {
CreateTemplate::new(self, guild_id, name)
}
/// Delete a template by ID and code.
pub fn delete_template(
&self,
guild_id: GuildId,
template_code: impl Into<String>,
) -> DeleteTemplate<'_> {
DeleteTemplate::new(self, guild_id, template_code)
}
/// Get a template by its code.
pub fn get_template(&self, template_code: impl Into<String>) -> GetTemplate<'_> {
GetTemplate::new(self, template_code)
}
/// Get a list of templates in a guild, by ID.
pub fn get_templates(&self, guild_id: GuildId) -> GetTemplates<'_> {
GetTemplates::new(self, guild_id)
}
/// Sync a template to the current state of the guild, by ID and code.
pub fn sync_template(
&self,
guild_id: GuildId,
template_code: impl Into<String>,
) -> SyncTemplate<'_> {
SyncTemplate::new(self, guild_id, template_code)
}
/// Update the template's metadata, by ID and code.
pub fn update_template(
&self,
guild_id: GuildId,
template_code: impl Into<String>,
) -> UpdateTemplate<'_> {
UpdateTemplate::new(self, guild_id, template_code)
}
/// Get a user's information by id.
pub fn user(&self, user_id: UserId) -> GetUser<'_> {
GetUser::new(self, user_id.to_string())
}
/// Update another user's voice state.
///
/// # Caveats
///
/// - `channel_id` must currently point to a stage channel.
/// - User must already have joined `channel_id`.
pub fn update_user_voice_state(
&self,
guild_id: GuildId,
user_id: UserId,
channel_id: ChannelId,
) -> UpdateUserVoiceState<'_> {
UpdateUserVoiceState::new(self, guild_id, user_id, channel_id)
}
/// Get a list of voice regions that can be used when creating a guild.
pub fn voice_regions(&self) -> GetVoiceRegions<'_> {
GetVoiceRegions::new(self)
}
/// Get a webhook by ID.
pub fn webhook(&self, id: WebhookId) -> GetWebhook<'_> {
GetWebhook::new(self, id)
}
/// Create a webhook in a channel.
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::ChannelId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("my token");
/// let channel_id = ChannelId(123);
///
/// let webhook = client
/// .create_webhook(channel_id, "Twily Bot")
/// .await?;
/// # Ok(()) }
/// ```
pub fn create_webhook(
&self,
channel_id: ChannelId,
name: impl Into<String>,
) -> CreateWebhook<'_> {
CreateWebhook::new(self, channel_id, name)
}
/// Delete a webhook by its ID.
pub fn delete_webhook(&self, id: WebhookId) -> DeleteWebhook<'_> {
DeleteWebhook::new(self, id)
}
/// Update a webhook by ID.
pub fn update_webhook(&self, webhook_id: WebhookId) -> UpdateWebhook<'_> {
UpdateWebhook::new(self, webhook_id)
}
/// Update a webhook, with a token, by ID.
pub fn update_webhook_with_token(
&self,
webhook_id: WebhookId,
token: impl Into<String>,
) -> UpdateWebhookWithToken<'_> {
UpdateWebhookWithToken::new(self, webhook_id, token)
}
/// Executes a webhook, sending a message to its channel.
///
/// You can only specify one of [`content`], [`embeds`], or [`file`].
///
/// # Examples
///
/// ```rust,no_run
/// # use twilight_http::Client;
/// # use twilight_model::id::WebhookId;
/// #
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("my token");
/// let id = WebhookId(432);
/// #
/// let webhook = client
/// .execute_webhook(id, "webhook token")
/// .content("Pinkie...")
/// .await?;
/// # Ok(()) }
/// ```
///
/// [`content`]: crate::request::channel::webhook::ExecuteWebhook::content
/// [`embeds`]: crate::request::channel::webhook::ExecuteWebhook::embeds
/// [`file`]: crate::request::channel::webhook::ExecuteWebhook::file
pub fn execute_webhook(
&self,
webhook_id: WebhookId,
token: impl Into<String>,
) -> ExecuteWebhook<'_> {
ExecuteWebhook::new(self, webhook_id, token)
}
/// Get a webhook message by [`WebhookId`], token, and [`MessageId`].
///
/// [`WebhookId`]: twilight_model::id::WebhookId
/// [`MessageId`]: twilight_model::id::MessageId
pub fn webhook_message(
&self,
webhook_id: WebhookId,
token: impl Into<String>,
message_id: MessageId,
) -> GetWebhookMessage<'_> {
GetWebhookMessage::new(self, webhook_id, token, message_id)
}
/// Update a message executed by a webhook.
///
/// # Examples
///
/// ```no_run
/// # use twilight_http::Client;
/// use twilight_model::id::{MessageId, WebhookId};
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("token");
/// client.update_webhook_message(WebhookId(1), "token here", MessageId(2))
/// .content(Some("new message content".to_owned()))?
/// .await?;
/// # Ok(()) }
/// ```
pub fn update_webhook_message(
&self,
webhook_id: WebhookId,
token: impl Into<String>,
message_id: MessageId,
) -> UpdateWebhookMessage<'_> {
UpdateWebhookMessage::new(self, webhook_id, token, message_id)
}
/// Delete a message executed by a webhook.
///
/// # Examples
///
/// ```no_run
/// # use twilight_http::Client;
/// use twilight_model::id::{MessageId, WebhookId};
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = Client::new("token");
/// client
/// .delete_webhook_message(WebhookId(1), "token here", MessageId(2))
/// .await?;
/// # Ok(()) }
/// ```
pub fn delete_webhook_message(
&self,
webhook_id: WebhookId,
token: impl Into<String>,
message_id: MessageId,
) -> DeleteWebhookMessage<'_> {
DeleteWebhookMessage::new(self, webhook_id, token, message_id)
}
/// Respond to an interaction, by ID and token.
pub fn interaction_callback(
&self,
interaction_id: InteractionId,
interaction_token: impl Into<String>,
response: InteractionResponse,
) -> InteractionCallback<'_> {
InteractionCallback::new(self, interaction_id, interaction_token, response)
}
/// Edit the original message, by its token.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn update_interaction_original(
&self,
interaction_token: impl Into<String>,
) -> Result<UpdateOriginalResponse<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(UpdateOriginalResponse::new(
self,
application_id,
interaction_token,
))
}
/// Delete the original message, by its token.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn delete_interaction_original(
&self,
interaction_token: impl Into<String>,
) -> Result<DeleteOriginalResponse<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(DeleteOriginalResponse::new(
self,
application_id,
interaction_token,
))
}
/// Create a followup message, by an interaction token.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn create_followup_message(
&self,
interaction_token: impl Into<String>,
) -> Result<CreateFollowupMessage<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(CreateFollowupMessage::new(
self,
application_id,
interaction_token,
))
}
/// Edit a followup message, by an interaction token.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn update_followup_message(
&self,
interaction_token: impl Into<String>,
message_id: MessageId,
) -> Result<UpdateFollowupMessage<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(UpdateFollowupMessage::new(
self,
application_id,
interaction_token,
message_id,
))
}
/// Delete a followup message by interaction token and the message's ID.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn delete_followup_message(
&self,
interaction_token: impl Into<String>,
message_id: MessageId,
) -> Result<DeleteFollowupMessage<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(DeleteFollowupMessage::new(
self,
application_id,
interaction_token,
message_id,
))
}
/// Create a new command in a guild.
///
/// The name must be between 3 and 32 characters in length, and the
/// description must be between 1 and 100 characters in length. Creating a
/// guild command with the same name as an already-existing guild command in
/// the same guild will overwrite the old command. See [the discord docs]
/// for more information.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
///
/// Returns an [`InteractionErrorType::CommandNameValidationFailed`]
/// error type if the command name is not between 3 and 32 characters.
///
/// Returns an [`InteractionErrorType::CommandDescriptionValidationFailed`]
/// error type if the command description is not between 1 and
/// 100 characters.
///
/// [the discord docs]: https://discord.com/developers/docs/interactions/slash-commands#create-guild-application-command
pub fn create_guild_command(
&self,
guild_id: GuildId,
name: impl Into<String>,
description: impl Into<String>,
) -> Result<CreateGuildCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
CreateGuildCommand::new(&self, application_id, guild_id, name, description)
}
/// Fetch all commands for a guild, by ID.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn get_guild_commands(
&self,
guild_id: GuildId,
) -> Result<GetGuildCommands<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(GetGuildCommands::new(self, application_id, guild_id))
}
/// Edit a command in a guild, by ID.
///
/// You must specify a name and description. See [the discord docs] for more
/// information.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
///
/// [the discord docs]: https://discord.com/developers/docs/interactions/slash-commands#edit-guild-application-command
pub fn update_guild_command(
&self,
guild_id: GuildId,
command_id: CommandId,
) -> Result<UpdateGuildCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(UpdateGuildCommand::new(
self,
application_id,
guild_id,
command_id,
))
}
/// Delete a command in a guild, by ID.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn delete_guild_command(
&self,
guild_id: GuildId,
command_id: CommandId,
) -> Result<DeleteGuildCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(DeleteGuildCommand::new(
self,
application_id,
guild_id,
command_id,
))
}
/// Set a guild's commands.
///
/// This method is idempotent: it can be used on every start, without being
/// ratelimited if there aren't changes to the commands.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn set_guild_commands(
&self,
guild_id: GuildId,
commands: Vec<Command>,
) -> Result<SetGuildCommands<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(SetGuildCommands::new(
self,
application_id,
guild_id,
commands,
))
}
/// Create a new global command.
///
/// The name must be between 3 and 32 characters in length, and the
/// description must be between 1 and 100 characters in length. Creating a
/// command with the same name as an already-existing global command will
/// overwrite the old command. See [the discord docs] for more information.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
///
/// Returns an [`InteractionErrorType::CommandNameValidationFailed`]
/// error type if the command name is not between 3 and 32 characters.
///
/// Returns an [`InteractionErrorType::CommandDescriptionValidationFailed`]
/// error type if the command description is not between 1 and
/// 100 characters.
///
/// [the discord docs]: https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command
pub fn create_global_command(
&self,
name: impl Into<String>,
description: impl Into<String>,
) -> Result<CreateGlobalCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
CreateGlobalCommand::new(self, application_id, name, description)
}
/// Fetch all global commands for your application.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn get_global_commands(&self) -> Result<GetGlobalCommands<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(GetGlobalCommands::new(self, application_id))
}
/// Edit a global command, by ID.
///
/// You must specify a name and description. See [the discord docs] for more
/// information.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
///
/// [the discord docs]: https://discord.com/developers/docs/interactions/slash-commands#edit-global-application-command
pub fn update_global_command(
&self,
command_id: CommandId,
) -> Result<UpdateGlobalCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(UpdateGlobalCommand::new(self, application_id, command_id))
}
/// Delete a global command, by ID.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn delete_global_command(
&self,
command_id: CommandId,
) -> Result<DeleteGlobalCommand<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(DeleteGlobalCommand::new(self, application_id, command_id))
}
/// Set global commands.
///
/// This method is idempotent: it can be used on every start, without being
/// ratelimited if there aren't changes to the commands.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn set_global_commands(
&self,
commands: Vec<Command>,
) -> Result<SetGlobalCommands<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(SetGlobalCommands::new(self, application_id, commands))
}
/// Fetch command permissions for a command from the current application
/// in a guild.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn get_command_permissions(
&self,
guild_id: GuildId,
command_id: CommandId,
) -> Result<GetCommandPermissions<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(GetCommandPermissions::new(
&self,
application_id,
guild_id,
command_id,
))
}
/// Fetch command permissions for all commands from the current
/// application in a guild.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn get_guild_command_permissions(
&self,
guild_id: GuildId,
) -> Result<GetGuildCommandPermissions<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
Ok(GetGuildCommandPermissions::new(
self,
application_id,
guild_id,
))
}
/// Update command permissions for a single command in a guild.
///
/// This overwrites the command permissions so the full set of permissions
/// have to be sent every time.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn update_command_permissions(
&self,
guild_id: GuildId,
command_id: CommandId,
permissions: Vec<CommandPermissions>,
) -> Result<UpdateCommandPermissions<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
UpdateCommandPermissions::new(self, application_id, guild_id, command_id, permissions)
}
/// Update command permissions for all commands in a guild.
///
/// This overwrites the command permissions so the full set of permissions
/// have to be sent every time.
///
/// # Errors
///
/// Returns an [`InteractionErrorType::ApplicationIdNotPresent`]
/// error type if an application ID has not been configured via
/// [`Client::set_application_id`].
pub fn set_command_permissions(
&self,
guild_id: GuildId,
permissions: impl Iterator<Item = (CommandId, CommandPermissions)>,
) -> Result<SetCommandPermissions<'_>, InteractionError> {
let application_id = self.application_id().ok_or(InteractionError {
kind: InteractionErrorType::ApplicationIdNotPresent,
})?;
SetCommandPermissions::new(self, application_id, guild_id, permissions)
}
/// Execute a request, returning the response.
///
/// # Errors
///
/// Returns an [`ErrorType::Unauthorized`] error type if the configured
/// token has become invalid due to expiration, revokation, etc.
#[allow(clippy::too_many_lines)]
pub async fn raw(&self, request: Request) -> Result<Response<Body>, Error> {
if self.state.token_invalid.load(Ordering::Relaxed) {
return Err(Error {
kind: ErrorType::Unauthorized,
source: None,
});
}
let Request {
body,
form,
headers: req_headers,
method,
path: bucket,
path_str: path,
use_authorization_token,
} = request;
let protocol = if self.state.use_http { "http" } else { "https" };
let host = self.state.proxy.as_deref().unwrap_or("discord.com");
let url = format!("{}://{}/api/v{}/{}", protocol, host, API_VERSION, path);
#[cfg(feature = "tracing")]
tracing::debug!("URL: {:?}", url);
let mut builder = hyper::Request::builder()
.method(method.into_hyper())
.uri(&url);
if use_authorization_token {
if let Some(ref token) = self.state.token {
let value = HeaderValue::from_str(&token).map_err(|source| {
#[allow(clippy::borrow_interior_mutable_const)]
let name = AUTHORIZATION.to_string();
Error {
kind: ErrorType::CreatingHeader { name },
source: Some(Box::new(source)),
}
})?;
if let Some(headers) = builder.headers_mut() {
headers.insert(AUTHORIZATION, value);
}
}
}
let user_agent = HeaderValue::from_static(concat!(
"DiscordBot (",
env!("CARGO_PKG_HOMEPAGE"),
", ",
env!("CARGO_PKG_VERSION"),
") Twilight-rs",
));
if let Some(headers) = builder.headers_mut() {
if let Some(form) = &form {
if let Ok(content_type) = HeaderValue::try_from(form.content_type()) {
headers.insert(CONTENT_TYPE, content_type);
}
} else if let Some(bytes) = &body {
let len = bytes.len();
headers.insert(CONTENT_LENGTH, len.into());
let content_type = HeaderValue::from_static("application/json");
headers.insert(CONTENT_TYPE, content_type);
}
headers.insert(USER_AGENT, user_agent);
if let Some(req_headers) = req_headers {
for (maybe_name, value) in req_headers {
if let Some(name) = maybe_name {
headers.insert(name, value);
}
}
}
if let Some(default_headers) = &self.state.default_headers {
for (name, value) in default_headers {
headers.insert(name, HeaderValue::from(value));
}
}
}
let req = if let Some(form) = form {
let form_bytes = form.build();
if let Some(headers) = builder.headers_mut() {
headers.insert(CONTENT_LENGTH, form_bytes.len().into());
};
builder
.body(Body::from(form_bytes))
.map_err(|source| Error {
kind: ErrorType::BuildingRequest,
source: Some(Box::new(source)),
})?
} else if let Some(bytes) = body {
builder.body(Body::from(bytes)).map_err(|source| Error {
kind: ErrorType::BuildingRequest,
source: Some(Box::new(source)),
})?
} else if method == Method::Put || method == Method::Post || method == Method::Patch {
if let Some(headers) = builder.headers_mut() {
headers.insert(CONTENT_LENGTH, 0.into());
}
builder.body(Body::empty()).map_err(|source| Error {
kind: ErrorType::BuildingRequest,
source: Some(Box::new(source)),
})?
} else {
builder.body(Body::empty()).map_err(|source| Error {
kind: ErrorType::BuildingRequest,
source: Some(Box::new(source)),
})?
};
let inner = self.state.http.request(req);
let fut = time::timeout(self.state.timeout, inner);
let ratelimiter = match self.state.ratelimiter.as_ref() {
Some(ratelimiter) => ratelimiter,
None => {
return fut
.await
.map_err(|source| Error {
kind: ErrorType::RequestTimedOut,
source: Some(Box::new(source)),
})?
.map_err(|source| Error {
kind: ErrorType::RequestError,
source: Some(Box::new(source)),
});
}
};
let rx = ratelimiter.get(bucket).await;
let tx = rx.await.map_err(|source| Error {
kind: ErrorType::RequestCanceled,
source: Some(Box::new(source)),
})?;
let resp = fut
.await
.map_err(|source| Error {
kind: ErrorType::RequestTimedOut,
source: Some(Box::new(source)),
})?
.map_err(|source| Error {
kind: ErrorType::RequestError,
source: Some(Box::new(source)),
})?;
// If the API sent back an Unauthorized response, then the client's
// configured token is permanently invalid and future requests must be
// ignored to avoid API bans.
if resp.status() == StatusCode::UNAUTHORIZED {
self.state.token_invalid.store(true, Ordering::Relaxed);
}
match RatelimitHeaders::try_from(resp.headers()) {
Ok(v) => {
let _res = tx.send(Some(v));
}
#[allow(unused_variables)]
Err(why) => {
#[cfg(feature = "tracing")]
tracing::warn!("header parsing failed: {:?}; {:?}", why, resp);
let _res = tx.send(None);
}
}
Ok(resp)
}
/// Execute a request, chunking and deserializing the response.
///
/// # Errors
///
/// Returns an [`ErrorType::Unauthorized`] error type if the configured
/// token has become invalid due to expiration, revokation, etc.
pub async fn request<T: DeserializeOwned>(&self, request: Request) -> Result<T, Error> {
let resp = self.make_request(request).await?;
let mut buf = body::aggregate(resp.into_body())
.await
.map_err(|source| Error {
kind: ErrorType::ChunkingResponse,
source: Some(Box::new(source)),
})?;
let mut bytes = vec![0; buf.remaining()];
buf.copy_to_slice(&mut bytes);
let result = crate::json::from_slice(&mut bytes);
result.map_err(|source| Error {
kind: ErrorType::Parsing {
body: bytes.clone(),
},
source: Some(Box::new(source)),
})
}
pub(crate) async fn request_bytes(&self, request: Request) -> Result<Bytes, Error> {
let resp = self.make_request(request).await?;
hyper::body::to_bytes(resp.into_body())
.await
.map_err(|source| Error {
kind: ErrorType::ChunkingResponse,
source: Some(Box::new(source)),
})
}
/// Execute a request, checking only that the response was a success.
///
/// This will not chunk and deserialize the body of the response.
///
/// # Errors
///
/// Returns an [`ErrorType::Unauthorized`] error type if the configured
/// token has become invalid due to expiration, revokation, etc.
pub async fn verify(&self, request: Request) -> Result<(), Error> {
self.make_request(request).await?;
Ok(())
}
async fn make_request(&self, request: Request) -> Result<Response<Body>, Error> {
let resp = self.raw(request).await?;
let status = resp.status();
if status.is_success() {
return Ok(resp);
}
match status {
StatusCode::IM_A_TEAPOT => {
#[cfg(feature = "tracing")]
tracing::warn!(
"discord's api now runs off of teapots -- proceed to panic: {:?}",
resp,
);
}
StatusCode::TOO_MANY_REQUESTS => {
#[cfg(feature = "tracing")]
tracing::warn!("429 response: {:?}", resp);
}
StatusCode::SERVICE_UNAVAILABLE => {
return Err(Error {
kind: ErrorType::ServiceUnavailable { response: resp },
source: None,
});
}
_ => {}
}
let mut buf = hyper::body::aggregate(resp.into_body())
.await
.map_err(|source| Error {
kind: ErrorType::ChunkingResponse,
source: Some(Box::new(source)),
})?;
let mut bytes = vec![0; buf.remaining()];
buf.copy_to_slice(&mut bytes);
let error = crate::json::from_slice::<ApiError>(&mut bytes).map_err(|source| Error {
kind: ErrorType::Parsing {
body: bytes.clone(),
},
source: Some(Box::new(source)),
})?;
#[cfg(feature = "tracing")]
if let ApiError::General(ref general) = error {
use crate::api_error::ErrorCode;
if let ErrorCode::Other(num) = general.code {
tracing::debug!("got unknown API error code variant: {}; {:?}", num, error);
}
}
Err(Error {
kind: ErrorType::Response {
body: bytes,
error,
status,
},
source: None,
})
}
}