use crate::audio::AudioControl;
use crate::error::Result;
use crate::http::HttpClient;
use crate::models::user::User;
use crate::models::{
announce::{
Announce, AnnouncesType, ChannelAnnouncesToCreate, GuildAnnouncesToCreate, RecommendChannel,
},
api::{BotInfo, GatewayResponse, MessageResponse, PinsMessage},
channel::{
Channel, ChannelPermissions, ChannelRolesPermissions, ChannelSubType, ChannelType,
ChannelValueObject, PrivateType, SpeakPermission, UpdateChannelPermissions,
},
emoji::EmojiType,
guild::{
Guild, GuildMembersPager, GuildPager, GuildRole, GuildRoleMembers, GuildRoleMembersPager,
GuildRoles, Member, MemberAddRoleBody, MemberDeleteOptions, UpdateGuildMute,
UpdateGuildMuteResponse, UpdateResult, UpdateRole, normalize_delete_history_msg_days,
},
message::{
ApiMessage, Ark, C2CMessageParams, DirectMessageParams, DirectMessageSession,
DirectMessageToCreate, Embed, GroupMessageParams, Keyboard, KeyboardPayload,
MarkdownPayload, Media, Message, MessagePagerType, MessageParams, MessageToCreate,
MessagesPager, Reference, RichMediaMessage, SendType, SettingGuide, SettingGuideToCreate,
},
message_setting::MessageSetting,
permission::{
APIPermission, APIPermissionDemand, APIPermissionDemandIdentify,
APIPermissionDemandToCreate, APIPermissions,
},
schedule::{RemindType, Schedule, ScheduleWrapper},
webhook::{
HttpIdentity, HttpReady, HttpSession, WebhookValidationRequest, WebhookValidationResponse,
},
};
use crate::options::{OpenApiOption, Options};
use crate::reaction::{Emoji as ReactionEmoji, MessageReactionPager, ReactionUsers};
use crate::token::Token;
use base64::Engine;
use reqwest::Method;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::Serialize;
use serde_json::{Value, json};
use std::collections::HashMap;
use std::time::Duration;
use tracing::debug;
pub type APIVersion = u32;
#[allow(non_upper_case_globals)]
pub const APIv1: APIVersion = 1;
#[allow(non_upper_case_globals)]
pub const MaxIdleConns: usize = 3000;
#[allow(non_upper_case_globals)]
pub const HeaderCallbackAppID: &str = "X-Callback-AppID";
#[allow(non_snake_case)]
pub fn APIVersionString(version: APIVersion) -> String {
format!("v{version}")
}
#[derive(Clone)]
pub struct BotApi {
http: HttpClient,
app_id: String,
token: Option<Token>,
}
impl BotApi {
pub fn new(http: HttpClient) -> Self {
Self {
http,
app_id: String::new(),
token: None,
}
}
pub fn with_token(http: HttpClient, token: Token) -> Self {
let app_id = token.app_id().to_string();
Self {
http: http.with_union_app_id(&app_id),
app_id,
token: Some(token),
}
}
pub fn setup_from_template(
&self,
bot_app_id: impl Into<String>,
token: Token,
in_sandbox: bool,
) -> Result<Self> {
let app_id = bot_app_id.into();
Ok(Self {
http: self
.http
.with_sandbox(in_sandbox)?
.with_union_app_id(&app_id),
app_id,
token: Some(token),
})
}
pub fn setup(
bot_app_id: impl Into<String>,
secret: impl Into<String>,
in_sandbox: bool,
) -> Result<(Self, Token)> {
let token = Token::new(bot_app_id, secret);
let api = Self::new(HttpClient::new(crate::DEFAULT_TIMEOUT, in_sandbox)?);
let app_id = token.app_id().to_string();
Ok((
api.setup_from_template(app_id, token.clone(), in_sandbox)?,
token,
))
}
#[allow(non_snake_case)]
pub fn Setup(
bot_app_id: impl Into<String>,
secret: impl Into<String>,
in_sandbox: bool,
) -> Result<(Self, Token)> {
Self::setup(bot_app_id, secret, in_sandbox)
}
pub const fn version(&self) -> APIVersion {
APIv1
}
#[allow(non_snake_case)]
pub const fn Version(&self) -> APIVersion {
self.version()
}
pub fn with_timeout(&self, duration: Duration) -> Result<Self> {
Ok(Self {
http: self.http.with_timeout(duration)?,
app_id: self.app_id.clone(),
token: self.token.clone(),
})
}
#[allow(non_snake_case)]
pub fn WithTimeout(&self, duration: Duration) -> Result<Self> {
self.with_timeout(duration)
}
pub fn set_debug(&self, debug: bool) -> Self {
Self {
http: self.http.with_debug(debug),
app_id: self.app_id.clone(),
token: self.token.clone(),
}
}
#[allow(non_snake_case)]
pub fn SetDebug(&self, debug: bool) -> Self {
self.set_debug(debug)
}
pub fn token(&self) -> Option<&Token> {
self.token.as_ref()
}
pub fn get_app_id(&self) -> &str {
&self.app_id
}
#[allow(non_snake_case)]
pub fn GetAppID(&self) -> &str {
self.get_app_id()
}
fn token_required(&self) -> Result<&Token> {
self.token.as_ref().ok_or_else(|| {
crate::BotError::config(
"BotApi has no stored token; use NewOpenAPI/NewSandboxOpenAPI or explicit-token methods",
)
})
}
fn url_with_options(&self, path: &str, options: &Options) -> String {
options
.url
.clone()
.unwrap_or_else(|| format!("{}{}", self.http.base_url(), path))
}
fn no_options() -> Vec<OpenApiOption> {
Vec::new()
}
fn parse_message_response(response: Value) -> Result<Message> {
if response
.get("id")
.and_then(Value::as_str)
.unwrap_or_default()
.is_empty()
&& let Some(message) = response.get("message")
{
return serde_json::from_value(message.clone()).map_err(Into::into);
}
serde_json::from_value(response).map_err(Into::into)
}
pub async fn transport<B>(
&self,
token: &Token,
method: Method,
url: &str,
body: Option<&B>,
) -> Result<Vec<u8>>
where
B: Serialize + ?Sized,
{
self.http.transport(token, method, url, body).await
}
#[allow(non_snake_case)]
pub async fn Transport<B>(&self, method: Method, url: &str, body: Option<&B>) -> Result<Vec<u8>>
where
B: Serialize + ?Sized,
{
self.transport(self.token_required()?, method, url, body)
.await
}
pub fn trace_id(&self) -> String {
self.http.trace_id()
}
#[allow(non_snake_case)]
pub fn TraceID(&self) -> String {
self.trace_id()
}
#[allow(non_snake_case)]
pub async fn WS(
&self,
_params: Option<&HashMap<String, String>>,
_body: Option<&str>,
) -> Result<GatewayResponse> {
self.get_gateway(self.token_required()?).await
}
#[allow(non_snake_case)]
pub async fn Me(&self) -> Result<User> {
Ok(self.get_bot_info(self.token_required()?).await?.into())
}
#[allow(non_snake_case)]
pub async fn MeGuilds(&self, pager: &GuildPager) -> Result<Vec<Guild>> {
self.get_guilds_with_pager(self.token_required()?, pager)
.await
}
#[allow(non_snake_case)]
pub async fn Guild(&self, guild_id: &str) -> Result<Guild> {
self.get_guild(self.token_required()?, guild_id).await
}
#[allow(non_snake_case)]
pub async fn GuildMember(&self, guild_id: &str, user_id: &str) -> Result<Member> {
self.get_guild_member(self.token_required()?, guild_id, user_id)
.await
}
#[allow(non_snake_case)]
pub async fn GuildMembers(
&self,
guild_id: &str,
pager: &GuildMembersPager,
) -> Result<Vec<Member>> {
self.get_guild_members_with_pager(self.token_required()?, guild_id, pager)
.await
}
#[allow(non_snake_case)]
pub async fn GuildRoleMembers(
&self,
guild_id: &str,
role_id: &str,
pager: &GuildRoleMembersPager,
) -> Result<(Vec<Member>, String)> {
let members = self
.get_guild_role_members_with_pager(self.token_required()?, guild_id, role_id, pager)
.await?;
Ok((members.data, members.next))
}
#[allow(non_snake_case)]
pub async fn DeleteGuildMember(
&self,
guild_id: &str,
user_id: &str,
options: impl IntoIterator<Item = crate::models::guild::MemberDeleteOption>,
) -> Result<()> {
let mut delete_options = crate::models::guild::MemberDeleteOptions::new();
for option in options {
option(&mut delete_options);
}
self.delete_member_with_options(self.token_required()?, guild_id, user_id, &delete_options)
.await
}
#[allow(non_snake_case)]
pub async fn GuildMute(&self, guild_id: &str, mute: &UpdateGuildMute) -> Result<()> {
let token = self.token_required()?;
let path = format!("/guilds/{guild_id}/mute");
self.http
.patch(token, &path, None::<&()>, Some(mute))
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn Channel(&self, channel_id: &str) -> Result<Channel> {
self.get_channel(self.token_required()?, channel_id).await
}
#[allow(non_snake_case)]
pub async fn Channels(&self, guild_id: &str) -> Result<Vec<Channel>> {
self.get_channels(self.token_required()?, guild_id).await
}
#[allow(non_snake_case)]
pub async fn PostChannel(&self, guild_id: &str, value: &ChannelValueObject) -> Result<Channel> {
self.post_channel(self.token_required()?, guild_id, value)
.await
}
#[allow(non_snake_case)]
pub async fn PatchChannel(
&self,
channel_id: &str,
value: &ChannelValueObject,
) -> Result<Channel> {
self.patch_channel(self.token_required()?, channel_id, value)
.await
}
#[allow(non_snake_case)]
pub async fn DeleteChannel(&self, channel_id: &str) -> Result<()> {
self.delete_channel(self.token_required()?, channel_id)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn CreatePrivateChannel(
&self,
guild_id: &str,
value: &ChannelValueObject,
user_ids: Vec<String>,
) -> Result<Channel> {
self.create_private_channel(self.token_required()?, guild_id, value, user_ids)
.await
}
#[allow(non_snake_case)]
pub async fn ListVoiceChannelMembers(&self, channel_id: &str) -> Result<Vec<Member>> {
self.list_voice_channel_members(self.token_required()?, channel_id)
.await
}
#[allow(non_snake_case)]
pub async fn ChannelPermissions(
&self,
channel_id: &str,
user_id: &str,
) -> Result<ChannelPermissions> {
self.get_channel_user_permissions(self.token_required()?, channel_id, user_id)
.await
}
#[allow(non_snake_case)]
pub async fn PutChannelPermissions(
&self,
channel_id: &str,
user_id: &str,
permissions: &UpdateChannelPermissions,
) -> Result<()> {
self.put_channel_permissions(self.token_required()?, channel_id, user_id, permissions)
.await
}
#[allow(non_snake_case)]
pub async fn ChannelRolesPermissions(
&self,
channel_id: &str,
role_id: &str,
) -> Result<ChannelRolesPermissions> {
self.get_channel_role_permissions(self.token_required()?, channel_id, role_id)
.await
}
#[allow(non_snake_case)]
pub async fn PutChannelRolesPermissions(
&self,
channel_id: &str,
role_id: &str,
permissions: &UpdateChannelPermissions,
) -> Result<()> {
self.put_channel_roles_permissions(self.token_required()?, channel_id, role_id, permissions)
.await
}
#[allow(non_snake_case)]
pub async fn Message(&self, channel_id: &str, message_id: &str) -> Result<Message> {
self.Message_with_options(channel_id, message_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn Message_with_options<I, O>(
&self,
channel_id: &str,
message_id: &str,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.get_message(self.token_required()?, channel_id, message_id)
.await;
}
let path = format!("/channels/{channel_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::GET,
&url,
None::<&()>,
None::<&()>,
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn Messages(&self, channel_id: &str, pager: &MessagesPager) -> Result<Vec<Message>> {
self.Messages_with_options(channel_id, pager, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn Messages_with_options<I, O>(
&self,
channel_id: &str,
pager: &MessagesPager,
options: I,
) -> Result<Vec<Message>>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.get_messages(self.token_required()?, channel_id, pager)
.await;
}
let path = format!("/channels/{channel_id}/messages");
let url = self.url_with_options(&path, &opts);
let params = pager.query_params();
let response = self
.http
.request_json_url(
self.token_required()?,
Method::GET,
&url,
if params.is_empty() {
None
} else {
Some(¶ms)
},
None::<&()>,
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PostMessage(&self, channel_id: &str, msg: &MessageToCreate) -> Result<Message> {
self.PostMessage_with_options(channel_id, msg, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostMessage_with_options<I, O>(
&self,
channel_id: &str,
msg: &MessageToCreate,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_message_to_create(self.token_required()?, channel_id, msg)
.await;
}
let path = format!("/channels/{channel_id}/messages");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(msg),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PatchMessage(
&self,
channel_id: &str,
message_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
self.PatchMessage_with_options(channel_id, message_id, msg, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PatchMessage_with_options<I, O>(
&self,
channel_id: &str,
message_id: &str,
msg: &MessageToCreate,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.patch_message_to_create(self.token_required()?, channel_id, message_id, msg)
.await;
}
let path = format!("/channels/{channel_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::PATCH,
&url,
None::<&()>,
Some(msg),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn RetractMessage(&self, channel_id: &str, message_id: &str) -> Result<()> {
self.RetractMessage_with_options(channel_id, message_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn RetractMessage_with_options<I, O>(
&self,
channel_id: &str,
message_id: &str,
options: I,
) -> Result<()>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.recall_message(
self.token_required()?,
channel_id,
message_id,
Some(opts.hide_tip),
)
.await;
}
let path = format!("/channels/{channel_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let query = opts
.hide_tip
.then(|| HashMap::from([("hidetip", "true".to_string())]));
self.http
.request_json_url(
self.token_required()?,
Method::DELETE,
&url,
query.as_ref(),
None::<&()>,
)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn PostSettingGuide(
&self,
channel_id: &str,
at_user_ids: Vec<String>,
) -> Result<Message> {
self.PostSettingGuide_with_options(channel_id, at_user_ids, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostSettingGuide_with_options<I, O>(
&self,
channel_id: &str,
at_user_ids: Vec<String>,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_setting_guide_message(self.token_required()?, channel_id, at_user_ids)
.await;
}
let content = at_user_ids
.iter()
.map(|user_id| format!("<@{user_id}>"))
.collect::<String>();
let body = SettingGuideToCreate {
content: Some(content),
setting_guide: None,
};
let path = format!("/channels/{channel_id}/settingguide");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(&body),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PostGroupMessage(
&self,
group_id: &str,
msg: impl Into<ApiMessage>,
) -> Result<Message> {
self.PostGroupMessage_with_options(group_id, msg, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostGroupMessage_with_options<I, O>(
&self,
group_id: &str,
msg: impl Into<ApiMessage>,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let msg = msg.into();
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_group_api_message(self.token_required()?, group_id, &msg)
.await;
}
let route = match msg.send_type() {
SendType::RichMedia => format!("/v2/groups/{group_id}/files"),
_ => format!("/v2/groups/{group_id}/messages"),
};
let url = self.url_with_options(&route, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(&msg),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PostC2CMessage(
&self,
user_id: &str,
msg: impl Into<ApiMessage>,
) -> Result<Message> {
self.PostC2CMessage_with_options(user_id, msg, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostC2CMessage_with_options<I, O>(
&self,
user_id: &str,
msg: impl Into<ApiMessage>,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let msg = msg.into();
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_c2c_api_message(self.token_required()?, user_id, &msg)
.await;
}
let route = match msg.send_type() {
SendType::RichMedia => format!("/v2/users/{user_id}/files"),
_ => format!("/v2/users/{user_id}/messages"),
};
let url = self.url_with_options(&route, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(&msg),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn RetractC2CMessage(&self, user_id: &str, message_id: &str) -> Result<()> {
self.RetractC2CMessage_with_options(user_id, message_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn RetractC2CMessage_with_options<I, O>(
&self,
user_id: &str,
message_id: &str,
options: I,
) -> Result<()>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.retract_c2c_message(
self.token_required()?,
user_id,
message_id,
Some(opts.hide_tip),
)
.await;
}
let path = format!("/v2/users/{user_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let query = opts
.hide_tip
.then(|| HashMap::from([("hidetip", "true".to_string())]));
self.http
.request_json_url(
self.token_required()?,
Method::DELETE,
&url,
query.as_ref(),
None::<&()>,
)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn RetractGroupMessage(&self, group_id: &str, message_id: &str) -> Result<()> {
self.RetractGroupMessage_with_options(group_id, message_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn RetractGroupMessage_with_options<I, O>(
&self,
group_id: &str,
message_id: &str,
options: I,
) -> Result<()>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.retract_group_message(
self.token_required()?,
group_id,
message_id,
Some(opts.hide_tip),
)
.await;
}
let path = format!("/v2/groups/{group_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let query = opts
.hide_tip
.then(|| HashMap::from([("hidetip", "true".to_string())]));
self.http
.request_json_url(
self.token_required()?,
Method::DELETE,
&url,
query.as_ref(),
None::<&()>,
)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn CreateDirectMessage(
&self,
dm: &DirectMessageToCreate,
) -> Result<DirectMessageSession> {
self.CreateDirectMessage_with_options(dm, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn CreateDirectMessage_with_options<I, O>(
&self,
dm: &DirectMessageToCreate,
options: I,
) -> Result<DirectMessageSession>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self.create_direct_message(self.token_required()?, dm).await;
}
let url = self.url_with_options("/users/@me/dms", &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(dm),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PostDirectMessage(
&self,
dm: &DirectMessageSession,
msg: &MessageToCreate,
) -> Result<Message> {
self.PostDirectMessage_with_options(dm, msg, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostDirectMessage_with_options<I, O>(
&self,
dm: &DirectMessageSession,
msg: &MessageToCreate,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let guild_id = dm.guild_id.as_deref().ok_or_else(|| {
crate::BotError::invalid_data("direct message session missing guild_id")
})?;
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_direct_message(self.token_required()?, guild_id, msg)
.await;
}
let path = format!("/dms/{guild_id}/messages");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(msg),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn RetractDMMessage(&self, guild_id: &str, message_id: &str) -> Result<()> {
self.RetractDMMessage_with_options(guild_id, message_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn RetractDMMessage_with_options<I, O>(
&self,
guild_id: &str,
message_id: &str,
options: I,
) -> Result<()>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.retract_dm_message(
self.token_required()?,
guild_id,
message_id,
Some(opts.hide_tip),
)
.await;
}
let path = format!("/dms/{guild_id}/messages/{message_id}");
let url = self.url_with_options(&path, &opts);
let query = opts
.hide_tip
.then(|| HashMap::from([("hidetip", "true".to_string())]));
self.http
.request_json_url(
self.token_required()?,
Method::DELETE,
&url,
query.as_ref(),
None::<&()>,
)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn PostDMSettingGuide(
&self,
dm: &DirectMessageSession,
jump_guild_id: &str,
) -> Result<Message> {
self.PostDMSettingGuide_with_options(dm, jump_guild_id, Self::no_options())
.await
}
#[allow(non_snake_case)]
pub async fn PostDMSettingGuide_with_options<I, O>(
&self,
dm: &DirectMessageSession,
jump_guild_id: &str,
options: I,
) -> Result<Message>
where
I: IntoIterator<Item = O>,
O: Into<OpenApiOption>,
{
let guild_id = dm.guild_id.as_deref().ok_or_else(|| {
crate::BotError::invalid_data("direct message session missing guild_id")
})?;
let opts = Options::from_options(options);
if opts.url.is_none() {
return self
.post_dm_setting_guide_message(self.token_required()?, guild_id, jump_guild_id)
.await;
}
let body = SettingGuideToCreate {
content: None,
setting_guide: Some(SettingGuide {
guild_id: jump_guild_id.to_string(),
}),
};
let path = format!("/dms/{guild_id}/settingguide");
let url = self.url_with_options(&path, &opts);
let response = self
.http
.request_json_url(
self.token_required()?,
Method::POST,
&url,
None::<&()>,
Some(&body),
)
.await?;
Ok(serde_json::from_value(response)?)
}
#[allow(non_snake_case)]
pub async fn PostAudio(
&self,
channel_id: &str,
audio_control: &AudioControl,
) -> Result<AudioControl> {
self.post_audio(self.token_required()?, channel_id, audio_control)
.await
}
#[allow(non_snake_case)]
pub async fn PutMic(&self, channel_id: &str) -> Result<()> {
self.on_microphone(self.token_required()?, channel_id).await
}
#[allow(non_snake_case)]
pub async fn DeleteMic(&self, channel_id: &str) -> Result<()> {
self.off_microphone(self.token_required()?, channel_id)
.await
}
#[allow(non_snake_case)]
pub async fn Roles(&self, guild_id: &str) -> Result<GuildRoles> {
self.get_guild_roles(self.token_required()?, guild_id).await
}
#[allow(non_snake_case)]
pub async fn PostRole(&self, guild_id: &str, role: &GuildRole) -> Result<UpdateResult> {
self.create_guild_role_with_update(self.token_required()?, guild_id, role.clone())
.await
}
#[allow(non_snake_case)]
pub async fn PatchRole(
&self,
guild_id: &str,
role_id: &str,
role: &GuildRole,
) -> Result<UpdateResult> {
self.update_guild_role_with_update(self.token_required()?, guild_id, role_id, role.clone())
.await
}
#[allow(non_snake_case)]
pub async fn DeleteRole(&self, guild_id: &str, role_id: &str) -> Result<()> {
self.delete_guild_role(self.token_required()?, guild_id, role_id)
.await
}
#[allow(non_snake_case)]
pub async fn MemberAddRole(
&self,
guild_id: &str,
role_id: &str,
user_id: &str,
value: &MemberAddRoleBody,
) -> Result<()> {
self.member_add_role(self.token_required()?, guild_id, role_id, user_id, value)
.await
}
#[allow(non_snake_case)]
pub async fn MemberDeleteRole(
&self,
guild_id: &str,
role_id: &str,
user_id: &str,
value: &MemberAddRoleBody,
) -> Result<()> {
self.member_delete_role(self.token_required()?, guild_id, role_id, user_id, value)
.await
}
#[allow(non_snake_case)]
pub async fn MemberMute(
&self,
guild_id: &str,
user_id: &str,
mute: &UpdateGuildMute,
) -> Result<()> {
let token = self.token_required()?;
let path = format!("/guilds/{guild_id}/members/{user_id}/mute");
self.http
.patch(token, &path, None::<&()>, Some(mute))
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn MultiMemberMute(
&self,
guild_id: &str,
mute: &UpdateGuildMute,
) -> Result<UpdateGuildMuteResponse> {
self.multi_member_mute(self.token_required()?, guild_id, mute)
.await
}
#[allow(non_snake_case)]
pub async fn CreateChannelAnnounces(
&self,
channel_id: &str,
announce: &ChannelAnnouncesToCreate,
) -> Result<Announce> {
self.create_channel_announce(self.token_required()?, channel_id, &announce.message_id)
.await
}
#[allow(non_snake_case)]
pub async fn DeleteChannelAnnounces(&self, channel_id: &str, message_id: &str) -> Result<()> {
self.delete_channel_announce(self.token_required()?, channel_id, message_id)
.await
}
#[allow(non_snake_case)]
pub async fn CleanChannelAnnounces(&self, channel_id: &str) -> Result<()> {
self.clean_channel_announces(self.token_required()?, channel_id)
.await
}
#[allow(non_snake_case)]
pub async fn CreateGuildAnnounces(
&self,
guild_id: &str,
announce: &GuildAnnouncesToCreate,
) -> Result<Announce> {
if !announce.recommend_channels.is_empty() {
self.create_recommend_announce(
self.token_required()?,
guild_id,
AnnouncesType::from(announce.announces_type as u8),
announce.recommend_channels.clone(),
)
.await
} else {
self.create_guild_announce(
self.token_required()?,
guild_id,
&announce.channel_id,
&announce.message_id,
)
.await
}
}
#[allow(non_snake_case)]
pub async fn DeleteGuildAnnounces(&self, guild_id: &str, message_id: &str) -> Result<()> {
self.delete_guild_announce(self.token_required()?, guild_id, message_id)
.await
}
#[allow(non_snake_case)]
pub async fn CleanGuildAnnounces(&self, guild_id: &str) -> Result<()> {
self.clean_guild_announces(self.token_required()?, guild_id)
.await
}
#[allow(non_snake_case)]
pub async fn ListSchedules(&self, channel_id: &str, since: u64) -> Result<Vec<Schedule>> {
let since = since.to_string();
self.get_schedules(self.token_required()?, channel_id, Some(since.as_str()))
.await
}
#[allow(non_snake_case)]
pub async fn GetSchedule(&self, channel_id: &str, schedule_id: &str) -> Result<Schedule> {
self.get_schedule(self.token_required()?, channel_id, schedule_id)
.await
}
#[allow(non_snake_case)]
pub async fn CreateSchedule(&self, channel_id: &str, schedule: &Schedule) -> Result<Schedule> {
self.create_schedule_with_model(self.token_required()?, channel_id, schedule)
.await
}
#[allow(non_snake_case)]
pub async fn ModifySchedule(
&self,
channel_id: &str,
schedule_id: &str,
schedule: &Schedule,
) -> Result<Schedule> {
self.update_schedule_with_model(self.token_required()?, channel_id, schedule_id, schedule)
.await
}
#[allow(non_snake_case)]
pub async fn DeleteSchedule(&self, channel_id: &str, schedule_id: &str) -> Result<()> {
self.delete_schedule(self.token_required()?, channel_id, schedule_id)
.await?;
Ok(())
}
#[allow(non_snake_case)]
pub async fn GetAPIPermissions(&self, guild_id: &str) -> Result<APIPermissions> {
self.get_api_permissions(self.token_required()?, guild_id)
.await
}
#[allow(non_snake_case)]
pub async fn RequireAPIPermissions(
&self,
guild_id: &str,
demand: &APIPermissionDemandToCreate,
) -> Result<APIPermissionDemand> {
self.require_api_permissions(self.token_required()?, guild_id, demand)
.await
}
#[allow(non_snake_case)]
pub async fn AddPins(&self, channel_id: &str, message_id: &str) -> Result<PinsMessage> {
self.put_pin(self.token_required()?, channel_id, message_id)
.await
}
#[allow(non_snake_case)]
pub async fn DeletePins(&self, channel_id: &str, message_id: &str) -> Result<()> {
self.delete_pin(self.token_required()?, channel_id, message_id)
.await
}
#[allow(non_snake_case)]
pub async fn CleanPins(&self, channel_id: &str) -> Result<()> {
self.clean_pins(self.token_required()?, channel_id).await
}
#[allow(non_snake_case)]
pub async fn GetPins(&self, channel_id: &str) -> Result<PinsMessage> {
self.get_pins(self.token_required()?, channel_id).await
}
#[allow(non_snake_case)]
pub async fn CreateMessageReaction(
&self,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
) -> Result<()> {
self.create_message_reaction(self.token_required()?, channel_id, message_id, emoji)
.await
}
#[allow(non_snake_case)]
pub async fn DeleteOwnMessageReaction(
&self,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
) -> Result<()> {
self.delete_own_message_reaction(self.token_required()?, channel_id, message_id, emoji)
.await
}
#[allow(non_snake_case)]
pub async fn GetMessageReactionUsers(
&self,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
pager: &MessageReactionPager,
) -> Result<ReactionUsers> {
self.get_message_reaction_users(
self.token_required()?,
channel_id,
message_id,
emoji,
pager,
)
.await
}
#[allow(non_snake_case)]
pub async fn PutInteraction(&self, interaction_id: &str, body: &str) -> Result<()> {
self.put_interaction(self.token_required()?, interaction_id, body)
.await
}
#[allow(non_snake_case)]
pub async fn CreateSession(&self, identity: HttpIdentity) -> Result<HttpReady> {
self.create_session(self.token_required()?, &identity).await
}
#[allow(non_snake_case)]
pub async fn CheckSessions(&self) -> Result<Vec<HttpSession>> {
self.check_sessions(self.token_required()?).await
}
#[allow(non_snake_case)]
pub async fn SessionList(&self) -> Result<Vec<HttpSession>> {
self.session_list(self.token_required()?).await
}
#[allow(non_snake_case)]
pub async fn RemoveSession(&self, session_id: &str) -> Result<()> {
self.remove_session(self.token_required()?, session_id)
.await
}
#[allow(non_snake_case)]
pub async fn GetMessageSetting(&self, guild_id: &str) -> Result<MessageSetting> {
self.get_message_setting(self.token_required()?, guild_id)
.await
}
pub async fn get_bot_info(&self, token: &Token) -> Result<BotInfo> {
debug!("Getting bot info");
let response = self.http.get(token, "/users/@me", None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_gateway(&self, token: &Token) -> Result<GatewayResponse> {
debug!("Getting gateway URL");
let response = self.http.get(token, "/gateway/bot", None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_guild(&self, token: &Token, guild_id: &str) -> Result<Guild> {
debug!("Getting guild {}", guild_id);
let path = format!("/guilds/{guild_id}");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_message_setting(
&self,
token: &Token,
guild_id: &str,
) -> Result<MessageSetting> {
debug!("Getting message setting for guild {}", guild_id);
let path = format!("/guilds/{guild_id}/message/setting");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_guilds(
&self,
token: &Token,
guild_id: Option<&str>,
limit: Option<u32>,
desc: Option<bool>,
) -> Result<Vec<Guild>> {
let mut pager = GuildPager::new();
if let Some(limit) = limit {
pager = pager.with_limit(limit);
}
if let Some(guild_id) = guild_id {
pager = if desc.unwrap_or(false) {
pager.with_before(guild_id)
} else {
pager.with_after(guild_id)
};
}
self.get_guilds_with_pager(token, &pager).await
}
pub async fn get_guilds_with_pager(
&self,
token: &Token,
pager: &GuildPager,
) -> Result<Vec<Guild>> {
debug!("Getting guilds");
let params = pager.query_params();
let response = self
.http
.get(
token,
"/users/@me/guilds",
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_guild_roles(&self, token: &Token, guild_id: &str) -> Result<GuildRoles> {
debug!("Getting guild roles for {}", guild_id);
let path = format!("/guilds/{guild_id}/roles");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_guild_role_with_update(
&self,
token: &Token,
guild_id: &str,
role: GuildRole,
) -> Result<UpdateResult> {
debug!("Creating guild role in {}", guild_id);
let body = UpdateRole::new(guild_id, role);
let path = format!("/guilds/{guild_id}/roles");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_guild_role(
&self,
token: &Token,
guild_id: &str,
name: Option<&str>,
color: Option<u32>,
hoist: Option<bool>,
) -> Result<GuildRole> {
let role = GuildRole {
name: name.unwrap_or_default().to_string(),
color: color.unwrap_or_default(),
hoist: hoist.map(u32::from).unwrap_or_default(),
..Default::default()
};
let result = self
.create_guild_role_with_update(token, guild_id, role)
.await?;
Ok(result.role.unwrap_or_default())
}
pub async fn update_guild_role_with_update(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
role: GuildRole,
) -> Result<UpdateResult> {
debug!("Updating guild role {} in {}", role_id, guild_id);
let body = UpdateRole::new(guild_id, role);
let path = format!("/guilds/{guild_id}/roles/{role_id}");
let response = self
.http
.patch(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn update_guild_role(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
name: Option<&str>,
color: Option<u32>,
hoist: Option<bool>,
) -> Result<GuildRole> {
let role = GuildRole {
id: role_id.to_string(),
name: name.unwrap_or_default().to_string(),
color: color.unwrap_or_default(),
hoist: hoist.map(u32::from).unwrap_or_default(),
..Default::default()
};
let result = self
.update_guild_role_with_update(token, guild_id, role_id, role)
.await?;
Ok(result.role.unwrap_or_default())
}
pub async fn delete_guild_role(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
) -> Result<()> {
debug!("Deleting guild role {} in {}", role_id, guild_id);
let path = format!("/guilds/{guild_id}/roles/{role_id}");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn create_guild_role_member(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
user_id: &str,
channel_id: Option<&str>,
) -> Result<()> {
debug!(
"Adding user {} to role {} in guild {}",
user_id, role_id, guild_id
);
let body = if let Some(channel_id) = channel_id {
MemberAddRoleBody::with_channel_id(channel_id)
} else {
MemberAddRoleBody::new()
};
self.member_add_role(token, guild_id, role_id, user_id, &body)
.await?;
Ok(())
}
pub async fn member_add_role(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
user_id: &str,
body: &MemberAddRoleBody,
) -> Result<()> {
let path = format!("/guilds/{guild_id}/members/{user_id}/roles/{role_id}");
self.http.put(token, &path, None::<&()>, Some(body)).await?;
Ok(())
}
pub async fn delete_guild_role_member(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
user_id: &str,
channel_id: Option<&str>,
) -> Result<()> {
debug!(
"Removing user {} from role {} in guild {}",
user_id, role_id, guild_id
);
let body = if let Some(channel_id) = channel_id {
MemberAddRoleBody::with_channel_id(channel_id)
} else {
MemberAddRoleBody::new()
};
self.member_delete_role(token, guild_id, role_id, user_id, &body)
.await?;
Ok(())
}
pub async fn member_delete_role(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
user_id: &str,
body: &MemberAddRoleBody,
) -> Result<()> {
let path = format!("/guilds/{guild_id}/members/{user_id}/roles/{role_id}");
self.http
.delete_with_body(token, &path, None::<&()>, Some(body))
.await?;
Ok(())
}
pub async fn get_guild_member(
&self,
token: &Token,
guild_id: &str,
user_id: &str,
) -> Result<Member> {
debug!("Getting guild member {} in {}", user_id, guild_id);
let path = format!("/guilds/{guild_id}/members/{user_id}");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_guild_members(
&self,
token: &Token,
guild_id: &str,
after: Option<&str>,
limit: Option<u32>,
) -> Result<Vec<Member>> {
let pager = GuildMembersPager::new(after.unwrap_or("0"), limit.unwrap_or(1).to_string());
self.get_guild_members_with_pager(token, guild_id, &pager)
.await
}
pub async fn get_guild_members_with_pager(
&self,
token: &Token,
guild_id: &str,
pager: &GuildMembersPager,
) -> Result<Vec<Member>> {
debug!(
"Getting guild members for {} with pager {:?}",
guild_id, pager
);
let path = format!("/guilds/{guild_id}/members");
let response = self.http.get(token, &path, Some(pager)).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_guild_role_members(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
start_index: Option<&str>,
limit: Option<u32>,
) -> Result<GuildRoleMembers> {
let pager =
GuildRoleMembersPager::new(start_index.unwrap_or("0"), limit.unwrap_or(1).to_string());
self.get_guild_role_members_with_pager(token, guild_id, role_id, &pager)
.await
}
pub async fn get_guild_role_members_with_pager(
&self,
token: &Token,
guild_id: &str,
role_id: &str,
pager: &GuildRoleMembersPager,
) -> Result<GuildRoleMembers> {
debug!(
"Getting role {} members for guild {} with pager {:?}",
role_id, guild_id, pager
);
let path = format!("/guilds/{guild_id}/roles/{role_id}/members");
let response = self.http.get(token, &path, Some(pager)).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn delete_member(
&self,
token: &Token,
guild_id: &str,
user_id: &str,
add_blacklist: Option<bool>,
delete_history_msg_days: Option<i32>,
) -> Result<()> {
let options = MemberDeleteOptions {
add_blacklist: add_blacklist.unwrap_or(false),
delete_history_msg_days: normalize_delete_history_msg_days(
delete_history_msg_days.unwrap_or(0),
),
};
self.delete_member_with_options(token, guild_id, user_id, &options)
.await
}
pub async fn delete_member_with_options(
&self,
token: &Token,
guild_id: &str,
user_id: &str,
options: &MemberDeleteOptions,
) -> Result<()> {
debug!("Deleting member {} from guild {}", user_id, guild_id);
let path = format!("/guilds/{guild_id}/members/{user_id}");
self.http
.delete_with_body(token, &path, None::<&()>, Some(options))
.await?;
Ok(())
}
pub async fn get_channel(&self, token: &Token, channel_id: &str) -> Result<Channel> {
debug!("Getting channel {}", channel_id);
let path = format!("/channels/{channel_id}");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_channels(&self, token: &Token, guild_id: &str) -> Result<Vec<Channel>> {
debug!("Getting channels for guild {}", guild_id);
let path = format!("/guilds/{guild_id}/channels");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_channel(
&self,
token: &Token,
guild_id: &str,
value: &ChannelValueObject,
) -> Result<Channel> {
debug!("Creating channel in guild {}", guild_id);
let path = format!("/guilds/{guild_id}/channels");
let response = self
.http
.post(token, &path, None::<&()>, Some(value))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_channel(
&self,
token: &Token,
guild_id: &str,
name: &str,
channel_type: ChannelType,
sub_type: ChannelSubType,
position: Option<u32>,
parent_id: Option<&str>,
private_type: Option<u32>,
private_user_ids: Option<Vec<String>>,
speak_permission: Option<u32>,
application_id: Option<&str>,
) -> Result<Channel> {
let value = ChannelValueObject {
name: Some(name.to_string()),
channel_type: Some(channel_type),
sub_type: Some(sub_type),
position: position.map(i64::from),
parent_id: parent_id.map(String::from),
private_type: private_type.map(|value| PrivateType::from(value as u8)),
private_user_ids,
speak_permission: speak_permission.map(|value| SpeakPermission::from(value as u8)),
application_id: application_id.map(String::from),
..Default::default()
};
self.post_channel(token, guild_id, &value).await
}
pub async fn create_private_channel(
&self,
token: &Token,
guild_id: &str,
value: &ChannelValueObject,
user_ids: Vec<String>,
) -> Result<Channel> {
let mut value = value.clone();
value.private_type = Some(PrivateType::AdminAndMember);
if !user_ids.is_empty() {
value.private_user_ids = Some(user_ids);
value.private_type = Some(PrivateType::OnlyAdmin);
}
self.post_channel(token, guild_id, &value).await
}
pub async fn update_channel(
&self,
token: &Token,
channel_id: &str,
name: Option<&str>,
position: Option<u32>,
parent_id: Option<&str>,
private_type: Option<u32>,
speak_permission: Option<u32>,
) -> Result<Channel> {
let value = ChannelValueObject {
name: name.map(String::from),
position: position.map(i64::from),
parent_id: parent_id.map(String::from),
private_type: private_type.map(|value| PrivateType::from(value as u8)),
speak_permission: speak_permission.map(|value| SpeakPermission::from(value as u8)),
..Default::default()
};
self.patch_channel(token, channel_id, &value).await
}
pub async fn patch_channel(
&self,
token: &Token,
channel_id: &str,
value: &ChannelValueObject,
) -> Result<Channel> {
debug!("Updating channel {}", channel_id);
let path = format!("/channels/{channel_id}");
let response = self
.http
.patch(token, &path, None::<&()>, Some(value))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn delete_channel(&self, token: &Token, channel_id: &str) -> Result<Channel> {
debug!("Deleting channel {}", channel_id);
let path = format!("/channels/{channel_id}");
let response = self.http.delete(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn list_voice_channel_members(
&self,
token: &Token,
channel_id: &str,
) -> Result<Vec<Member>> {
debug!("Listing voice channel members for channel {}", channel_id);
let path = format!("/channels/{channel_id}/voice/members");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_message(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
) -> Result<Message> {
debug!("Getting message {} in channel {}", message_id, channel_id);
let path = format!("/channels/{channel_id}/messages/{message_id}");
let response = self.http.get(token, &path, None::<&()>).await?;
Self::parse_message_response(response)
}
pub async fn get_messages(
&self,
token: &Token,
channel_id: &str,
pager: &MessagesPager,
) -> Result<Vec<Message>> {
debug!("Getting messages in channel {}", channel_id);
let params = pager.query_params();
let path = format!("/channels/{channel_id}/messages");
let response = self
.http
.get(
token,
&path,
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_messages_with_params(
&self,
token: &Token,
channel_id: &str,
pager_type: Option<MessagePagerType>,
message_id: Option<&str>,
limit: Option<u32>,
) -> Result<Vec<Message>> {
let pager = MessagesPager::new(pager_type, message_id, limit);
self.get_messages(token, channel_id, &pager).await
}
pub async fn post_message_to_create(
&self,
token: &Token,
channel_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
debug!("Sending message to channel {}", channel_id);
let path = format!("/channels/{channel_id}/messages");
let response = self.http.post(token, &path, None::<&()>, Some(msg)).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_message_api(
&self,
token: &Token,
channel_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
self.post_message_to_create(token, channel_id, msg).await
}
pub async fn patch_message_to_create(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
debug!("Editing message {} in channel {}", message_id, channel_id);
let path = format!("/channels/{channel_id}/messages/{message_id}");
let response = self
.http
.patch(token, &path, None::<&()>, Some(msg))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn patch_message_api(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
self.patch_message_to_create(token, channel_id, message_id, msg)
.await
}
pub async fn post_message_with_params(
&self,
token: &Token,
channel_id: &str,
params: MessageParams,
) -> Result<MessageResponse> {
debug!("Sending message to channel {}", channel_id);
let body = serde_json::to_value(MessageToCreate::from(params))?;
let path = format!("/channels/{channel_id}/messages");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn patch_message_with_params(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
params: MessageParams,
) -> Result<MessageResponse> {
debug!("Editing message {} in channel {}", message_id, channel_id);
let body = serde_json::to_value(MessageToCreate::from(params))?;
let path = format!("/channels/{channel_id}/messages/{message_id}");
let response = self
.http
.patch(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn patch_message(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
params: MessageParams,
) -> Result<MessageResponse> {
self.patch_message_with_params(token, channel_id, message_id, params)
.await
}
#[deprecated(since = "0.1.0", note = "Use post_message_with_params instead")]
pub async fn post_message(
&self,
token: &Token,
channel_id: &str,
content: Option<&str>,
embed: Option<&Embed>,
ark: Option<&Ark>,
message_reference: Option<&Reference>,
image: Option<&str>,
file_image: Option<&[u8]>,
msg_id: Option<&str>,
event_id: Option<&str>,
markdown: Option<&MarkdownPayload>,
keyboard: Option<&Keyboard>,
) -> Result<MessageResponse> {
let params = MessageParams {
content: content.map(|s| s.to_string()),
msg_type: None,
embed: embed.cloned(),
ark: ark.cloned(),
message_reference: message_reference.cloned(),
image: image.map(|s| s.to_string()),
file_image: file_image
.map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
msg_id: msg_id.map(|s| s.to_string()),
event_id: event_id.map(|s| s.to_string()),
markdown: markdown.cloned(),
keyboard: keyboard.cloned(),
timestamp: None,
msg_seq: None,
subscribe_id: None,
input_notify: None,
media: None,
prompt_keyboard: None,
action_button: None,
stream: None,
feature_id: None,
};
self.post_message_with_params(token, channel_id, params)
.await
}
pub async fn post_group_message_with_params(
&self,
token: &Token,
group_openid: &str,
params: GroupMessageParams,
) -> Result<MessageResponse> {
debug!("Sending group message to {}", group_openid);
let body = serde_json::to_value(MessageToCreate::from(params))?;
let path = format!("/v2/groups/{group_openid}/messages");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_group_api_message(
&self,
token: &Token,
group_openid: &str,
msg: &ApiMessage,
) -> Result<Message> {
debug!("Sending group message to {}", group_openid);
self.post_group_api_payload(token, group_openid, msg.send_type(), msg)
.await
}
pub async fn post_group_message_to_create(
&self,
token: &Token,
group_openid: &str,
msg: &MessageToCreate,
) -> Result<Message> {
self.post_group_api_payload(token, group_openid, msg.send_type(), msg)
.await
}
pub async fn post_group_rich_media_message(
&self,
token: &Token,
group_openid: &str,
msg: &RichMediaMessage,
) -> Result<Message> {
self.post_group_api_payload(token, group_openid, msg.send_type(), msg)
.await
}
async fn post_group_api_payload<T: Serialize + ?Sized>(
&self,
token: &Token,
group_openid: &str,
send_type: SendType,
msg: &T,
) -> Result<Message> {
let path = match send_type {
SendType::RichMedia => format!("/v2/groups/{group_openid}/files"),
_ => format!("/v2/groups/{group_openid}/messages"),
};
let response = self.http.post(token, &path, None::<&()>, Some(msg)).await?;
Ok(serde_json::from_value(response)?)
}
#[deprecated(since = "0.1.0", note = "Use post_group_message_with_params instead")]
pub async fn post_group_message(
&self,
token: &Token,
group_openid: &str,
msg_type: Option<u32>,
content: Option<&str>,
embed: Option<&Embed>,
ark: Option<&Ark>,
message_reference: Option<&Reference>,
media: Option<&Media>,
msg_id: Option<&str>,
msg_seq: Option<u32>,
event_id: Option<&str>,
markdown: Option<&MarkdownPayload>,
keyboard: Option<&KeyboardPayload>,
) -> Result<MessageResponse> {
let params = GroupMessageParams {
msg_type: msg_type.unwrap_or(0),
content: content.map(|s| s.to_string()),
embed: embed.cloned(),
ark: ark.cloned(),
message_reference: message_reference.cloned(),
media: media.cloned(),
msg_id: msg_id.map(|s| s.to_string()),
msg_seq,
event_id: event_id.map(|s| s.to_string()),
markdown: markdown.cloned(),
keyboard: keyboard.cloned(),
timestamp: None,
subscribe_id: None,
input_notify: None,
prompt_keyboard: None,
action_button: None,
stream: None,
feature_id: None,
};
self.post_group_message_with_params(token, group_openid, params)
.await
}
pub async fn post_c2c_message_with_params(
&self,
token: &Token,
openid: &str,
params: C2CMessageParams,
) -> Result<MessageResponse> {
debug!("Sending C2C message to {}", openid);
let body = serde_json::to_value(MessageToCreate::from(params))?;
let path = format!("/v2/users/{openid}/messages");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_c2c_api_message(
&self,
token: &Token,
openid: &str,
msg: &ApiMessage,
) -> Result<Message> {
debug!("Sending C2C message to {}", openid);
self.post_c2c_api_payload(token, openid, msg.send_type(), msg)
.await
}
pub async fn post_c2c_message_to_create(
&self,
token: &Token,
openid: &str,
msg: &MessageToCreate,
) -> Result<Message> {
self.post_c2c_api_payload(token, openid, msg.send_type(), msg)
.await
}
pub async fn post_c2c_rich_media_message(
&self,
token: &Token,
openid: &str,
msg: &RichMediaMessage,
) -> Result<Message> {
self.post_c2c_api_payload(token, openid, msg.send_type(), msg)
.await
}
async fn post_c2c_api_payload<T: Serialize + ?Sized>(
&self,
token: &Token,
openid: &str,
send_type: SendType,
msg: &T,
) -> Result<Message> {
let path = match send_type {
SendType::RichMedia => format!("/v2/users/{openid}/files"),
_ => format!("/v2/users/{openid}/messages"),
};
let response = self.http.post(token, &path, None::<&()>, Some(msg)).await?;
Ok(serde_json::from_value(response)?)
}
#[deprecated(since = "0.1.0", note = "Use post_c2c_message_with_params instead")]
pub async fn post_c2c_message(
&self,
token: &Token,
openid: &str,
msg_type: Option<u32>,
content: Option<&str>,
embed: Option<&Embed>,
ark: Option<&Ark>,
message_reference: Option<&Reference>,
media: Option<&Media>,
msg_id: Option<&str>,
msg_seq: Option<u32>,
event_id: Option<&str>,
markdown: Option<&MarkdownPayload>,
keyboard: Option<&KeyboardPayload>,
) -> Result<MessageResponse> {
let params = C2CMessageParams {
msg_type: msg_type.unwrap_or(0),
content: content.map(|s| s.to_string()),
embed: embed.cloned(),
ark: ark.cloned(),
message_reference: message_reference.cloned(),
media: media.cloned(),
msg_id: msg_id.map(|s| s.to_string()),
msg_seq,
event_id: event_id.map(|s| s.to_string()),
markdown: markdown.cloned(),
keyboard: keyboard.cloned(),
timestamp: None,
subscribe_id: None,
input_notify: None,
prompt_keyboard: None,
action_button: None,
stream: None,
feature_id: None,
};
self.post_c2c_message_with_params(token, openid, params)
.await
}
pub async fn post_dms_with_params(
&self,
token: &Token,
guild_id: &str,
params: DirectMessageParams,
) -> Result<MessageResponse> {
debug!("Sending direct message to guild session {}", guild_id);
let body = serde_json::to_value(MessageToCreate::from(params))?;
let path = format!("/dms/{guild_id}/messages");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_direct_message(
&self,
token: &Token,
guild_id: &str,
msg: &MessageToCreate,
) -> Result<Message> {
debug!("Sending direct message to guild {}", guild_id);
let path = format!("/dms/{guild_id}/messages");
let response = self.http.post(token, &path, None::<&()>, Some(msg)).await?;
Ok(serde_json::from_value(response)?)
}
#[deprecated(since = "0.1.0", note = "Use post_dms_with_params instead")]
pub async fn post_dms(
&self,
token: &Token,
guild_id: &str,
content: Option<&str>,
embed: Option<&Embed>,
ark: Option<&Ark>,
message_reference: Option<&Reference>,
image: Option<&str>,
file_image: Option<&[u8]>,
msg_id: Option<&str>,
event_id: Option<&str>,
markdown: Option<&MarkdownPayload>,
keyboard: Option<&Keyboard>,
) -> Result<MessageResponse> {
let params = DirectMessageParams {
content: content.map(|s| s.to_string()),
msg_type: None,
embed: embed.cloned(),
ark: ark.cloned(),
message_reference: message_reference.cloned(),
image: image.map(|s| s.to_string()),
file_image: file_image
.map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
msg_id: msg_id.map(|s| s.to_string()),
event_id: event_id.map(|s| s.to_string()),
markdown: markdown.cloned(),
keyboard: keyboard.cloned(),
timestamp: None,
msg_seq: None,
subscribe_id: None,
input_notify: None,
media: None,
prompt_keyboard: None,
action_button: None,
stream: None,
feature_id: None,
};
self.post_dms_with_params(token, guild_id, params).await
}
pub async fn create_direct_message(
&self,
token: &Token,
dm: &DirectMessageToCreate,
) -> Result<DirectMessageSession> {
debug!(
"Creating DM session for user {} from guild {}",
dm.recipient_id, dm.source_guild_id
);
let response = self
.http
.post(token, "/users/@me/dms", None::<&()>, Some(dm))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_dms(
&self,
token: &Token,
guild_id: &str,
user_id: &str,
) -> Result<DirectMessageSession> {
let dm = DirectMessageToCreate::new(guild_id, user_id);
self.create_direct_message(token, &dm).await
}
pub async fn post_setting_guide(
&self,
token: &Token,
channel_id: &str,
at_user_ids: Vec<String>,
) -> Result<MessageResponse> {
let content = at_user_ids
.iter()
.map(|user_id| format!("<@{user_id}>"))
.collect::<String>();
let body = SettingGuideToCreate {
content: Some(content),
setting_guide: None,
};
let path = format!("/channels/{channel_id}/settingguide");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_setting_guide_message(
&self,
token: &Token,
channel_id: &str,
at_user_ids: Vec<String>,
) -> Result<Message> {
let content = at_user_ids
.iter()
.map(|user_id| format!("<@{user_id}>"))
.collect::<String>();
let body = SettingGuideToCreate {
content: Some(content),
setting_guide: None,
};
let path = format!("/channels/{channel_id}/settingguide");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_dm_setting_guide(
&self,
token: &Token,
guild_id: &str,
jump_guild_id: &str,
) -> Result<MessageResponse> {
let body = SettingGuideToCreate {
content: None,
setting_guide: Some(SettingGuide {
guild_id: jump_guild_id.to_string(),
}),
};
let path = format!("/dms/{guild_id}/settingguide");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_dm_setting_guide_message(
&self,
token: &Token,
guild_id: &str,
jump_guild_id: &str,
) -> Result<Message> {
let body = SettingGuideToCreate {
content: None,
setting_guide: Some(SettingGuide {
guild_id: jump_guild_id.to_string(),
}),
};
let path = format!("/dms/{guild_id}/settingguide");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn recall_message(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
hidetip: Option<bool>,
) -> Result<()> {
debug!("Recalling message {} in channel {}", message_id, channel_id);
let mut params = HashMap::new();
params.insert(
"hidetip",
if hidetip.unwrap_or(false) {
"true"
} else {
"false"
}
.to_string(),
);
let path = format!("/channels/{channel_id}/messages/{message_id}");
self.http.delete(token, &path, Some(¶ms)).await?;
Ok(())
}
pub async fn retract_c2c_message(
&self,
token: &Token,
openid: &str,
message_id: &str,
hidetip: Option<bool>,
) -> Result<()> {
debug!("Retracting C2C message {} for {}", message_id, openid);
let mut params = HashMap::new();
if hidetip.unwrap_or(false) {
params.insert("hidetip", "true".to_string());
}
let path = format!("/v2/users/{openid}/messages/{message_id}");
self.http
.delete(
token,
&path,
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(())
}
pub async fn retract_group_message(
&self,
token: &Token,
group_openid: &str,
message_id: &str,
hidetip: Option<bool>,
) -> Result<()> {
debug!(
"Retracting group message {} for {}",
message_id, group_openid
);
let mut params = HashMap::new();
if hidetip.unwrap_or(false) {
params.insert("hidetip", "true".to_string());
}
let path = format!("/v2/groups/{group_openid}/messages/{message_id}");
self.http
.delete(
token,
&path,
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(())
}
pub async fn retract_dm_message(
&self,
token: &Token,
guild_id: &str,
message_id: &str,
hidetip: Option<bool>,
) -> Result<()> {
debug!("Retracting DM message {} in {}", message_id, guild_id);
let mut params = HashMap::new();
if hidetip.unwrap_or(false) {
params.insert("hidetip", "true".to_string());
}
let path = format!("/dms/{guild_id}/messages/{message_id}");
self.http
.delete(
token,
&path,
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(())
}
pub async fn update_audio(
&self,
token: &Token,
channel_id: &str,
audio_control: &AudioControl,
) -> Result<()> {
self.post_audio(token, channel_id, audio_control).await?;
Ok(())
}
pub async fn post_audio(
&self,
token: &Token,
channel_id: &str,
audio_control: &AudioControl,
) -> Result<AudioControl> {
debug!("Updating audio in channel {}", channel_id);
let path = format!("/channels/{channel_id}/audio");
let _response = self
.http
.post(token, &path, None::<&()>, Some(audio_control))
.await?;
Ok(audio_control.clone())
}
pub async fn on_microphone(&self, token: &Token, channel_id: &str) -> Result<()> {
debug!("Turning on microphone in channel {}", channel_id);
let path = format!("/channels/{channel_id}/mic");
self.http
.put(token, &path, None::<&()>, None::<&()>)
.await?;
Ok(())
}
pub async fn off_microphone(&self, token: &Token, channel_id: &str) -> Result<()> {
debug!("Turning off microphone in channel {}", channel_id);
let path = format!("/channels/{channel_id}/mic");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn mute_all(
&self,
token: &Token,
guild_id: &str,
mute_end_timestamp: Option<&str>,
mute_seconds: Option<&str>,
) -> Result<()> {
debug!("Muting all members in guild {}", guild_id);
let body = UpdateGuildMute::new(mute_end_timestamp, mute_seconds);
let path = format!("/guilds/{guild_id}/mute");
self.http
.patch(token, &path, None::<&()>, Some(&body))
.await?;
Ok(())
}
pub async fn cancel_mute_all(&self, token: &Token, guild_id: &str) -> Result<()> {
debug!("Canceling mute for all members in guild {}", guild_id);
let body = UpdateGuildMute::cancel();
let path = format!("/guilds/{guild_id}/mute");
self.http
.patch(token, &path, None::<&()>, Some(&body))
.await?;
Ok(())
}
pub async fn mute_member(
&self,
token: &Token,
guild_id: &str,
user_id: &str,
mute_end_timestamp: Option<&str>,
mute_seconds: Option<&str>,
) -> Result<()> {
debug!("Muting member {} in guild {}", user_id, guild_id);
let body = UpdateGuildMute::new(mute_end_timestamp, mute_seconds);
let path = format!("/guilds/{guild_id}/members/{user_id}/mute");
self.http
.patch(token, &path, None::<&()>, Some(&body))
.await?;
Ok(())
}
pub async fn mute_multi_member(
&self,
token: &Token,
guild_id: &str,
user_ids: Vec<String>,
mute_end_timestamp: Option<&str>,
mute_seconds: Option<&str>,
) -> Result<UpdateGuildMuteResponse> {
if user_ids.is_empty() {
return Err(crate::error::BotError::invalid_data("no user id param"));
}
let body = UpdateGuildMute::new_multi(user_ids, mute_end_timestamp, mute_seconds);
self.multi_member_mute(token, guild_id, &body).await
}
pub async fn cancel_mute_multi_member(
&self,
token: &Token,
guild_id: &str,
user_ids: Vec<String>,
) -> Result<UpdateGuildMuteResponse> {
if user_ids.is_empty() {
return Err(crate::error::BotError::invalid_data("no user id param"));
}
let body = UpdateGuildMute::cancel_multi(user_ids);
self.multi_member_mute(token, guild_id, &body).await
}
pub async fn multi_member_mute(
&self,
token: &Token,
guild_id: &str,
mute: &UpdateGuildMute,
) -> Result<UpdateGuildMuteResponse> {
if mute.user_ids.is_empty() {
return Err(crate::error::BotError::invalid_data("no user id param"));
}
debug!("Muting multiple members in guild {}", guild_id);
let path = format!("/guilds/{guild_id}/mute");
let response = self
.http
.patch(token, &path, None::<&()>, Some(mute))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_channel_user_permissions(
&self,
token: &Token,
channel_id: &str,
user_id: &str,
) -> Result<ChannelPermissions> {
debug!(
"Getting channel permissions for user {} in channel {}",
user_id, channel_id
);
let path = format!("/channels/{channel_id}/members/{user_id}/permissions");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn put_channel_permissions(
&self,
token: &Token,
channel_id: &str,
user_id: &str,
permissions: &UpdateChannelPermissions,
) -> Result<()> {
permissions.validate()?;
debug!(
"Updating channel permissions for user {} in channel {}",
user_id, channel_id
);
let path = format!("/channels/{channel_id}/members/{user_id}/permissions");
self.http
.put(token, &path, None::<&()>, Some(permissions))
.await?;
Ok(())
}
pub async fn update_channel_user_permissions(
&self,
token: &Token,
channel_id: &str,
user_id: &str,
add: Option<&str>,
remove: Option<&str>,
) -> Result<()> {
let permissions = UpdateChannelPermissions::new(add, remove);
self.put_channel_permissions(token, channel_id, user_id, &permissions)
.await
}
pub async fn get_channel_role_permissions(
&self,
token: &Token,
channel_id: &str,
role_id: &str,
) -> Result<ChannelRolesPermissions> {
debug!(
"Getting channel permissions for role {} in channel {}",
role_id, channel_id
);
let path = format!("/channels/{channel_id}/roles/{role_id}/permissions");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn put_channel_roles_permissions(
&self,
token: &Token,
channel_id: &str,
role_id: &str,
permissions: &UpdateChannelPermissions,
) -> Result<()> {
permissions.validate()?;
debug!(
"Updating channel permissions for role {} in channel {}",
role_id, channel_id
);
let path = format!("/channels/{channel_id}/roles/{role_id}/permissions");
self.http
.put(token, &path, None::<&()>, Some(permissions))
.await?;
Ok(())
}
pub async fn update_channel_role_permissions(
&self,
token: &Token,
channel_id: &str,
role_id: &str,
add: Option<&str>,
remove: Option<&str>,
) -> Result<()> {
let permissions = UpdateChannelPermissions::new(add, remove);
self.put_channel_roles_permissions(token, channel_id, role_id, &permissions)
.await
}
pub async fn put_reaction(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji_type: i32,
emoji_id: &str,
) -> Result<()> {
debug!(
"Adding reaction to message {} in channel {}",
message_id, channel_id
);
let path = format!(
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji_type}/{emoji_id}"
);
self.http
.put(token, &path, None::<&()>, None::<&()>)
.await?;
Ok(())
}
pub async fn create_message_reaction(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
) -> Result<()> {
self.put_reaction(token, channel_id, message_id, emoji.emoji_type, &emoji.id)
.await
}
pub async fn delete_reaction(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji_type: i32,
emoji_id: &str,
) -> Result<()> {
debug!(
"Removing reaction from message {} in channel {}",
message_id, channel_id
);
let path = format!(
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji_type}/{emoji_id}"
);
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn delete_own_message_reaction(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
) -> Result<()> {
self.delete_reaction(token, channel_id, message_id, emoji.emoji_type, &emoji.id)
.await
}
pub async fn put_interaction(
&self,
token: &Token,
interaction_id: &str,
body: &str,
) -> Result<()> {
debug!("Updating interaction {}", interaction_id);
let mut headers = HeaderMap::new();
let app_id = if self.app_id.is_empty() {
token.app_id()
} else {
&self.app_id
};
let app_id = HeaderValue::from_str(app_id)
.map_err(|e| crate::BotError::invalid_data(format!("Invalid app ID header: {e}")))?;
headers.insert(HeaderCallbackAppID, app_id);
let path = format!("/interactions/{interaction_id}");
self.http
.put_raw_with_headers(token, &path, None::<&()>, body, headers)
.await?;
Ok(())
}
pub async fn put_pin(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
) -> Result<PinsMessage> {
debug!("Pinning message {} in channel {}", message_id, channel_id);
let path = format!("/channels/{channel_id}/pins/{message_id}");
let response = self
.http
.put(token, &path, None::<&()>, None::<&()>)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn delete_pin(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
) -> Result<()> {
debug!("Unpinning message {} in channel {}", message_id, channel_id);
let path = format!("/channels/{channel_id}/pins/{message_id}");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn clean_pins(&self, token: &Token, channel_id: &str) -> Result<()> {
debug!("Clearing pinned messages in channel {}", channel_id);
let path = format!("/channels/{channel_id}/pins/all");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn get_pins(&self, token: &Token, channel_id: &str) -> Result<PinsMessage> {
debug!("Getting pinned messages in channel {}", channel_id);
let path = format!("/channels/{channel_id}/pins");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_group_file(
&self,
token: &Token,
group_openid: &str,
file_type: u32,
url: &str,
srv_send_msg: Option<bool>,
) -> Result<Value> {
debug!("Uploading group file to {}", group_openid);
let body = json!({
"file_type": file_type,
"url": url,
"srv_send_msg": srv_send_msg.unwrap_or(false)
});
let path = format!("/v2/groups/{group_openid}/files");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(response)
}
pub async fn post_c2c_file(
&self,
token: &Token,
openid: &str,
file_type: u32,
url: &str,
srv_send_msg: Option<bool>,
) -> Result<Value> {
debug!("Uploading C2C file to {}", openid);
let body = json!({
"file_type": file_type,
"url": url,
"srv_send_msg": srv_send_msg.unwrap_or(false)
});
let path = format!("/v2/users/{openid}/files");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(response)
}
pub async fn create_channel_announce(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
) -> Result<Announce> {
debug!(
"Creating channel announcement in channel {} for message {}",
channel_id, message_id
);
let body = json!({
"message_id": message_id
});
let path = format!("/channels/{channel_id}/announces");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn delete_channel_announce(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
) -> Result<()> {
debug!(
"Deleting announcement {} in channel {}",
message_id, channel_id
);
let path = format!("/channels/{channel_id}/announces/{message_id}");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn clean_channel_announces(&self, token: &Token, channel_id: &str) -> Result<()> {
debug!("Clearing announcements in channel {}", channel_id);
let path = format!("/channels/{channel_id}/announces/all");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn create_announce(
&self,
token: &Token,
guild_id: &str,
channel_id: &str,
message_id: &str,
) -> Result<Announce> {
debug!(
"Creating announcement in guild {} for message {}",
guild_id, message_id
);
let body = json!({
"channel_id": channel_id,
"message_id": message_id
});
let path = format!("/guilds/{guild_id}/announces");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_guild_announce(
&self,
token: &Token,
guild_id: &str,
channel_id: &str,
message_id: &str,
) -> Result<Announce> {
self.create_announce(token, guild_id, channel_id, message_id)
.await
}
pub async fn create_recommend_announce(
&self,
token: &Token,
guild_id: &str,
announces_type: AnnouncesType,
recommend_channels: Vec<RecommendChannel>,
) -> Result<Announce> {
debug!("Creating recommend announcement in guild {}", guild_id);
let body = json!({
"announces_type": u8::from(announces_type),
"recommend_channels": recommend_channels
});
let path = format!("/guilds/{guild_id}/announces");
let response = self
.http
.post(token, &path, None::<&()>, Some(&body))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_guild_recommend_announce(
&self,
token: &Token,
guild_id: &str,
announces_type: AnnouncesType,
recommend_channels: Vec<RecommendChannel>,
) -> Result<Announce> {
self.create_recommend_announce(token, guild_id, announces_type, recommend_channels)
.await
}
pub async fn delete_announce(
&self,
token: &Token,
guild_id: &str,
message_id: &str,
) -> Result<Value> {
debug!("Deleting announcement {} in guild {}", message_id, guild_id);
let path = format!("/guilds/{guild_id}/announces/{message_id}");
let response = self.http.delete(token, &path, None::<&()>).await?;
Ok(response)
}
pub async fn delete_guild_announce(
&self,
token: &Token,
guild_id: &str,
message_id: &str,
) -> Result<()> {
self.delete_announce(token, guild_id, message_id).await?;
Ok(())
}
pub async fn clean_guild_announces(&self, token: &Token, guild_id: &str) -> Result<()> {
debug!("Clearing announcements in guild {}", guild_id);
let path = format!("/guilds/{guild_id}/announces/all");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub async fn get_api_permissions(
&self,
token: &Token,
guild_id: &str,
) -> Result<APIPermissions> {
debug!("Getting permissions for guild {}", guild_id);
let path = format!("/guilds/{guild_id}/api_permission");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_permissions(
&self,
token: &Token,
guild_id: &str,
) -> Result<Vec<APIPermission>> {
Ok(self.get_api_permissions(token, guild_id).await?.api_list)
}
pub async fn require_api_permissions(
&self,
token: &Token,
guild_id: &str,
demand: &APIPermissionDemandToCreate,
) -> Result<APIPermissionDemand> {
debug!("Creating permission demand in guild {}", guild_id);
let path = format!("/guilds/{guild_id}/api_permission/demand");
let response = self
.http
.post(token, &path, None::<&()>, Some(demand))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn post_permission_demand(
&self,
token: &Token,
guild_id: &str,
channel_id: &str,
api_identify: APIPermissionDemandIdentify,
desc: &str,
) -> Result<APIPermissionDemand> {
debug!("Creating permission demand in guild {}", guild_id);
let demand = APIPermissionDemandToCreate::new(channel_id, api_identify, desc);
self.require_api_permissions(token, guild_id, &demand).await
}
pub async fn get_reaction_users(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji_type: EmojiType,
emoji_id: &str,
cookie: Option<&str>,
limit: Option<u32>,
) -> Result<ReactionUsers> {
debug!(
"Getting reaction users for message {} with emoji {}",
message_id, emoji_id
);
let mut params = HashMap::new();
params.insert("limit", limit.unwrap_or(20).to_string());
if let Some(cookie) = cookie {
params.insert("cookie", cookie.to_string());
}
let path = format!(
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji_type}/{emoji_id}",
emoji_type = u8::from(emoji_type)
);
let response = self.http.get(token, &path, Some(¶ms)).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_message_reaction_users(
&self,
token: &Token,
channel_id: &str,
message_id: &str,
emoji: &ReactionEmoji,
pager: &MessageReactionPager,
) -> Result<ReactionUsers> {
debug!(
"Getting reaction users for message {} with emoji {:?}",
message_id, emoji.id
);
let params = pager.query_params();
let path = format!(
"/channels/{channel_id}/messages/{message_id}/reactions/{emoji_type}/{emoji_id}",
emoji_type = emoji.emoji_type,
emoji_id = emoji.id
);
let response = self
.http
.get(
token,
&path,
if params.is_empty() {
None
} else {
Some(¶ms)
},
)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_schedules(
&self,
token: &Token,
channel_id: &str,
since: Option<&str>,
) -> Result<Vec<Schedule>> {
debug!("Getting schedules for channel {}", channel_id);
#[derive(Serialize)]
struct ScheduleQuery<'a> {
since: &'a str,
}
let query = since.map(|since| ScheduleQuery { since });
let path = format!("/channels/{channel_id}/schedules");
let response = self.http.get(token, &path, query.as_ref()).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn get_schedule(
&self,
token: &Token,
channel_id: &str,
schedule_id: &str,
) -> Result<Schedule> {
debug!("Getting schedule {} in channel {}", schedule_id, channel_id);
let path = format!("/channels/{channel_id}/schedules/{schedule_id}");
let response = self.http.get(token, &path, None::<&()>).await?;
Ok(serde_json::from_value(response)?)
}
pub async fn create_schedule(
&self,
token: &Token,
channel_id: &str,
name: &str,
start_timestamp: &str,
end_timestamp: &str,
jump_channel_id: &str,
remind_type: RemindType,
) -> Result<Schedule> {
let schedule = Schedule::new(
name,
start_timestamp,
end_timestamp,
Some(jump_channel_id.to_string()),
remind_type,
);
self.create_schedule_with_model(token, channel_id, &schedule)
.await
}
pub async fn create_schedule_with_model(
&self,
token: &Token,
channel_id: &str,
schedule: &Schedule,
) -> Result<Schedule> {
debug!(
"Creating schedule '{}' in channel {}",
schedule.name, channel_id
);
let wrapper = ScheduleWrapper::new(schedule.clone());
let path = format!("/channels/{channel_id}/schedules");
let response = self
.http
.post(token, &path, None::<&()>, Some(&wrapper))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn update_schedule(
&self,
token: &Token,
channel_id: &str,
schedule_id: &str,
name: &str,
start_timestamp: &str,
end_timestamp: &str,
jump_channel_id: &str,
remind_type: RemindType,
) -> Result<Schedule> {
let schedule = Schedule::new(
name,
start_timestamp,
end_timestamp,
Some(jump_channel_id.to_string()),
remind_type,
);
self.update_schedule_with_model(token, channel_id, schedule_id, &schedule)
.await
}
pub async fn update_schedule_with_model(
&self,
token: &Token,
channel_id: &str,
schedule_id: &str,
schedule: &Schedule,
) -> Result<Schedule> {
debug!(
"Updating schedule {} in channel {}",
schedule_id, channel_id
);
let wrapper = ScheduleWrapper::new(schedule.clone());
let path = format!("/channels/{channel_id}/schedules/{schedule_id}");
let response = self
.http
.patch(token, &path, None::<&()>, Some(&wrapper))
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn delete_schedule(
&self,
token: &Token,
channel_id: &str,
schedule_id: &str,
) -> Result<Value> {
debug!(
"Deleting schedule {} in channel {}",
schedule_id, channel_id
);
let path = format!("/channels/{channel_id}/schedules/{schedule_id}");
let response = self.http.delete(token, &path, None::<&()>).await?;
Ok(response)
}
pub async fn create_session(
&self,
token: &Token,
identity: &HttpIdentity,
) -> Result<HttpReady> {
debug!("Creating HTTP webhook session");
let response = self
.http
.post(
token,
"/gateway/webhook/sessions",
None::<&()>,
Some(identity),
)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn check_sessions(&self, token: &Token) -> Result<Vec<HttpSession>> {
debug!("Checking HTTP webhook sessions");
let mut params = HashMap::new();
params.insert("action", "check");
let response = self
.http
.patch(
token,
"/gateway/webhook/sessions",
Some(¶ms),
None::<&()>,
)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn session_list(&self, token: &Token) -> Result<Vec<HttpSession>> {
debug!("Listing HTTP webhook sessions");
let response = self
.http
.get(token, "/gateway/webhook/sessions", None::<&()>)
.await?;
Ok(serde_json::from_value(response)?)
}
pub async fn remove_session(&self, token: &Token, session_id: &str) -> Result<()> {
debug!("Removing HTTP webhook session {}", session_id);
let path = format!("/gateway/webhook/sessions/{session_id}");
self.http.delete(token, &path, None::<&()>).await?;
Ok(())
}
pub fn webhook_validation_response(
request: &WebhookValidationRequest,
signature: impl Into<String>,
data_version: impl Into<String>,
) -> WebhookValidationResponse {
WebhookValidationResponse {
plain_token: request.plain_token.clone(),
signature: signature.into(),
data_version: data_version.into(),
}
}
pub fn http(&self) -> &HttpClient {
&self.http
}
pub async fn close(&self) {
self.http.close().await;
}
}
impl std::fmt::Debug for BotApi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BotApi").field("http", &self.http).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::HttpClient;
#[test]
fn test_api_creation() {
let http = HttpClient::new(30, false).unwrap();
let api = BotApi::new(http);
assert!(!api.http().is_sandbox());
}
#[test]
fn test_base_helpers() {
let (api, token) = BotApi::Setup("app-id", "secret", true).unwrap();
assert_eq!(api.Version(), APIv1);
assert_eq!(APIVersionString(api.version()), "v1");
assert_eq!(token.app_id(), "app-id");
assert_eq!(api.GetAppID(), "app-id");
assert_eq!(api.http().union_app_id(), Some("app-id"));
assert!(api.http().is_sandbox());
let api = api.WithTimeout(Duration::from_secs(7)).unwrap();
assert_eq!(api.http().timeout(), Duration::from_secs(7));
assert_eq!(api.GetAppID(), "app-id");
let api = api.SetDebug(true);
assert!(api.http().debug_enabled());
assert_eq!(api.GetAppID(), "app-id");
assert_eq!(api.TraceID(), "");
}
#[test]
fn options_build_custom_urls() {
let api = BotApi::new(HttpClient::new(30, false).unwrap());
let options = Options::from_options([crate::WithURL("https://example.com/custom")]);
assert_eq!(
api.url_with_options("/channels/1/messages", &options),
"https://example.com/custom"
);
let options = Options::default();
assert_eq!(
api.url_with_options("/channels/1/messages", &options),
format!("{}{}", crate::DEFAULT_API_URL, "/channels/1/messages")
);
}
#[test]
fn hide_tip_option_sets_flag() {
let options = Options::from_options([crate::WithHideTip()]);
assert!(options.hide_tip);
assert!(options.url.is_none());
}
#[test]
fn message_response_accepts_legacy_wrapper() {
let message = BotApi::parse_message_response(serde_json::json!({
"message": {
"id": "msg-1",
"content": "wrapped",
"channel_id": "channel-1"
}
}))
.unwrap();
assert_eq!(message.id.as_deref(), Some("msg-1"));
assert_eq!(message.content.as_deref(), Some("wrapped"));
assert_eq!(message.channel_id.as_deref(), Some("channel-1"));
}
#[test]
fn message_response_keeps_direct_shape() {
let message = BotApi::parse_message_response(serde_json::json!({
"id": "msg-2",
"content": "direct"
}))
.unwrap();
assert_eq!(message.id.as_deref(), Some("msg-2"));
assert_eq!(message.content.as_deref(), Some("direct"));
}
}