#![allow(clippy::derive_partial_eq_without_eq)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::nonstandard_macro_braces)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::tabs_in_doc_comments)]
#![allow(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod admin_apps;
pub mod admin_apps_approved;
pub mod admin_apps_requests;
pub mod admin_apps_restricted;
pub mod admin_conversations;
pub mod admin_conversations_ekm;
pub mod admin_conversations_restrict_access;
pub mod admin_emoji;
pub mod admin_invite_requests;
pub mod admin_invite_requests_approved;
pub mod admin_invite_requests_denied;
pub mod admin_teams;
pub mod admin_teams_admins;
pub mod admin_teams_owners;
pub mod admin_teams_settings;
pub mod admin_usergroups;
pub mod admin_users;
pub mod admin_users_session;
pub mod api;
pub mod apps;
pub mod apps_event_authorizations;
pub mod apps_permissions;
pub mod apps_permissions_resources;
pub mod apps_permissions_scopes;
pub mod apps_permissions_users;
pub mod auth;
pub mod bots;
pub mod calls;
pub mod calls_participants;
pub mod chat;
pub mod chat_scheduled_messages;
pub mod conversations;
pub mod dialog;
pub mod dnd;
pub mod emoji;
pub mod files;
pub mod files_comments;
pub mod files_remote;
pub mod migration;
pub mod oauth;
pub mod oauth_v_2;
pub mod pins;
pub mod reactions;
pub mod reminders;
pub mod rtm;
pub mod search;
pub mod stars;
pub mod team;
pub mod team_profile;
#[cfg(test)]
mod tests;
pub mod types;
pub mod usergroups;
pub mod usergroups_users;
pub mod users;
pub mod users_profile;
#[doc(hidden)]
pub mod utils;
pub mod views;
pub mod workflows;
use anyhow::{anyhow, Error, Result};
pub const DEFAULT_HOST: &str = "https://slack.com/api";
mod progenitor_support {
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
const PATH_SET: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'`')
.add(b'{')
.add(b'}');
#[allow(dead_code)]
pub(crate) fn encode_path(pc: &str) -> String {
utf8_percent_encode(pc, PATH_SET).to_string()
}
}
use std::convert::TryInto;
use std::env;
use std::ops::Add;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
const TOKEN_ENDPOINT: &str = "https://slack.com/api/oauth.v2.access";
const USER_CONSENT_ENDPOINT: &str = "https://slack.com/oauth/v2/authorize";
#[derive(Clone)]
pub struct Client {
host: String,
token: Arc<RwLock<InnerToken>>,
client_id: String,
client_secret: String,
redirect_uri: String,
auto_refresh: bool,
client: reqwest_middleware::ClientWithMiddleware,
}
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
pub struct AccessToken {
#[serde(
default,
skip_serializing_if = "String::is_empty",
deserialize_with = "crate::utils::deserialize_null_string::deserialize"
)]
pub token_type: String,
#[serde(
default,
skip_serializing_if = "String::is_empty",
deserialize_with = "crate::utils::deserialize_null_string::deserialize"
)]
pub access_token: String,
#[serde(default)]
pub expires_in: i64,
#[serde(
default,
skip_serializing_if = "String::is_empty",
deserialize_with = "crate::utils::deserialize_null_string::deserialize"
)]
pub refresh_token: String,
#[serde(default, alias = "x_refresh_token_expires_in")]
pub refresh_token_expires_in: i64,
#[serde(
default,
skip_serializing_if = "String::is_empty",
deserialize_with = "crate::utils::deserialize_null_string::deserialize"
)]
pub scope: String,
}
const REFRESH_THRESHOLD: Duration = Duration::from_secs(60);
#[derive(Debug, Clone)]
struct InnerToken {
access_token: String,
refresh_token: String,
expires_at: Option<Instant>,
}
impl Client {
pub fn new<I, K, R, T, Q>(
client_id: I,
client_secret: K,
redirect_uri: R,
token: T,
refresh_token: Q,
) -> Self
where
I: ToString,
K: ToString,
R: ToString,
T: ToString,
Q: ToString,
{
let retry_policy =
reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
let client = reqwest::Client::builder().build();
match client {
Ok(c) => {
let client = reqwest_middleware::ClientBuilder::new(c)
.with(reqwest_tracing::TracingMiddleware::default())
.with(reqwest_conditional_middleware::ConditionalMiddleware::new(
reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
|req: &reqwest::Request| req.try_clone().is_some(),
))
.build();
Client {
host: DEFAULT_HOST.to_string(),
client_id: client_id.to_string(),
client_secret: client_secret.to_string(),
redirect_uri: redirect_uri.to_string(),
token: Arc::new(RwLock::new(InnerToken {
access_token: token.to_string(),
refresh_token: refresh_token.to_string(),
expires_at: None,
})),
auto_refresh: false,
client,
}
}
Err(e) => panic!("creating reqwest client failed: {:?}", e),
}
}
pub fn set_auto_access_token_refresh(&mut self, enabled: bool) -> &mut Self {
self.auto_refresh = enabled;
self
}
pub async fn set_expires_at(&self, expires_at: Option<Instant>) -> &Self {
self.token.write().await.expires_at = expires_at;
self
}
pub async fn expires_at(&self) -> Option<Instant> {
self.token.read().await.expires_at
}
pub async fn set_expires_in(&self, expires_in: i64) -> &Self {
self.token.write().await.expires_at = Self::compute_expires_at(expires_in);
self
}
pub async fn expires_in(&self) -> Option<Duration> {
self.token
.read()
.await
.expires_at
.map(|i| i.duration_since(Instant::now()))
}
pub async fn is_expired(&self) -> Option<bool> {
self.token
.read()
.await
.expires_at
.map(|expiration| expiration <= Instant::now())
}
fn compute_expires_at(expires_in: i64) -> Option<Instant> {
let seconds_valid = expires_in
.try_into()
.ok()
.map(Duration::from_secs)
.and_then(|dur| dur.checked_sub(REFRESH_THRESHOLD))
.or_else(|| Some(Duration::from_secs(0)));
seconds_valid.map(|seconds_valid| Instant::now().add(seconds_valid))
}
pub fn with_host<H>(&self, host: H) -> Self
where
H: ToString,
{
let mut c = self.clone();
c.host = host.to_string();
c
}
pub fn new_from_env<T, R>(token: T, refresh_token: R) -> Self
where
T: ToString,
R: ToString,
{
let client_id = env::var("SLACK_CLIENT_ID").expect("must set SLACK_CLIENT_ID");
let client_secret = env::var("SLACK_CLIENT_SECRET").expect("must set SLACK_CLIENT_SECRET");
let redirect_uri = env::var("SLACK_REDIRECT_URI").expect("must set SLACK_REDIRECT_URI");
Client::new(client_id, client_secret, redirect_uri, token, refresh_token)
}
pub fn user_consent_url(&self, scopes: &[String]) -> String {
let state = uuid::Uuid::new_v4();
let url = format!(
"{}?client_id={}&response_type=code&redirect_uri={}&state={}",
USER_CONSENT_ENDPOINT, self.client_id, self.redirect_uri, state
);
if scopes.is_empty() {
return url;
}
format!("{}&scope={}", url, scopes.join(" "))
}
pub async fn refresh_access_token(&self) -> Result<AccessToken> {
let response = {
let refresh_token = &self.token.read().await.refresh_token;
if refresh_token.is_empty() {
anyhow!("refresh token cannot be empty");
}
let mut headers = reqwest::header::HeaderMap::new();
headers.append(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
let params = [
("grant_type", "refresh_token"),
("refresh_token", refresh_token),
("client_id", &self.client_id),
("client_secret", &self.client_secret),
("redirect_uri", &self.redirect_uri),
];
let client = reqwest::Client::new();
client
.post(TOKEN_ENDPOINT)
.headers(headers)
.form(¶ms)
.basic_auth(&self.client_id, Some(&self.client_secret))
.send()
.await?
};
let t: AccessToken = response.json().await?;
let refresh_token = self.token.read().await.refresh_token.clone();
*self.token.write().await = InnerToken {
access_token: t.access_token.clone(),
refresh_token,
expires_at: Self::compute_expires_at(t.expires_in),
};
Ok(t)
}
pub async fn get_access_token(&mut self, code: &str, state: &str) -> Result<AccessToken> {
let mut headers = reqwest::header::HeaderMap::new();
headers.append(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
let params = [
("grant_type", "authorization_code"),
("code", code),
("client_id", &self.client_id),
("client_secret", &self.client_secret),
("redirect_uri", &self.redirect_uri),
("state", state),
];
let client = reqwest::Client::new();
let resp = client
.post(TOKEN_ENDPOINT)
.headers(headers)
.form(¶ms)
.basic_auth(&self.client_id, Some(&self.client_secret))
.send()
.await?;
let t: AccessToken = resp.json().await?;
*self.token.write().await = InnerToken {
access_token: t.access_token.clone(),
refresh_token: t.refresh_token.clone(),
expires_at: Self::compute_expires_at(t.expires_in),
};
Ok(t)
}
async fn url_and_auth(&self, uri: &str) -> Result<(reqwest::Url, Option<String>)> {
let parsed_url = uri.parse::<reqwest::Url>();
let auth = format!("Bearer {}", self.token.read().await.access_token);
parsed_url.map(|u| (u, Some(auth))).map_err(Error::from)
}
async fn make_request(
&self,
method: &reqwest::Method,
uri: &str,
body: Option<reqwest::Body>,
) -> Result<reqwest::Request> {
let u = if uri.starts_with("https://") {
uri.to_string()
} else {
(self.host.clone() + uri).to_string()
};
let (url, auth) = self.url_and_auth(&u).await?;
let instance = <&Client>::clone(&self);
let mut req = instance.client.request(method.clone(), url);
req = req.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
req = req.header(
reqwest::header::CONTENT_TYPE,
reqwest::header::HeaderValue::from_static("application/json"),
);
if let Some(auth_str) = auth {
req = req.header(http::header::AUTHORIZATION, &*auth_str);
}
if let Some(body) = body {
req = req.body(body);
}
Ok(req.build()?)
}
async fn request_raw(
&self,
method: reqwest::Method,
uri: &str,
body: Option<reqwest::Body>,
) -> Result<reqwest::Response> {
if self.auto_refresh {
let expired = self.is_expired().await;
match expired {
Some(true) => {
self.refresh_access_token().await?;
}
Some(false) => (),
None => (),
}
}
let req = self.make_request(&method, uri, body).await?;
let resp = self.client.execute(req).await?;
Ok(resp)
}
async fn request<Out>(
&self,
method: reqwest::Method,
uri: &str,
body: Option<reqwest::Body>,
) -> Result<Out>
where
Out: serde::de::DeserializeOwned + 'static + Send,
{
let response = self.request_raw(method, uri, body).await?;
let status = response.status();
let response_body = response.bytes().await?;
if status.is_success() {
log::debug!("Received successful response. Read payload.");
let parsed_response = if status == http::StatusCode::NO_CONTENT
|| std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
{
serde_json::from_str("null")
} else {
serde_json::from_slice::<Out>(&response_body)
};
parsed_response.map_err(Error::from)
} else {
let error = if response_body.is_empty() {
anyhow!("code: {}, empty response", status)
} else {
anyhow!(
"code: {}, error: {:?}",
status,
String::from_utf8_lossy(&response_body),
)
};
Err(error)
}
}
async fn request_with_links<Out>(
&self,
method: http::Method,
uri: &str,
body: Option<reqwest::Body>,
) -> Result<(Option<hyperx::header::Link>, Out)>
where
Out: serde::de::DeserializeOwned + 'static + Send,
{
let response = self.request_raw(method, uri, body).await?;
let status = response.status();
let link = response
.headers()
.get(http::header::LINK)
.and_then(|l| l.to_str().ok())
.and_then(|l| l.parse().ok());
let response_body = response.bytes().await?;
if status.is_success() {
log::debug!("Received successful response. Read payload.");
let parsed_response = if status == http::StatusCode::NO_CONTENT
|| std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
{
serde_json::from_str("null")
} else {
serde_json::from_slice::<Out>(&response_body)
};
parsed_response.map(|out| (link, out)).map_err(Error::from)
} else {
let error = if response_body.is_empty() {
anyhow!("code: {}, empty response", status)
} else {
anyhow!(
"code: {}, error: {:?}",
status,
String::from_utf8_lossy(&response_body),
)
};
Err(error)
}
}
#[allow(dead_code)]
async fn post_form<Out>(&self, uri: &str, form: reqwest::multipart::Form) -> Result<Out>
where
Out: serde::de::DeserializeOwned + 'static + Send,
{
let u = if uri.starts_with("https://") {
uri.to_string()
} else {
(self.host.clone() + uri).to_string()
};
let (url, auth) = self.url_and_auth(&u).await?;
let instance = <&Client>::clone(&self);
let mut req = instance.client.request(http::Method::POST, url);
req = req.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
if let Some(auth_str) = auth {
req = req.header(http::header::AUTHORIZATION, &*auth_str);
}
req = req.multipart(form);
let response = req.send().await?;
let status = response.status();
let response_body = response.bytes().await?;
if status.is_success() {
log::debug!("Received successful response. Read payload.");
let parsed_response = if status == http::StatusCode::NO_CONTENT
|| std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
{
serde_json::from_str("null")
} else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
serde_json::from_value(serde_json::json!(&String::from_utf8(
response_body.to_vec()
)?))
} else {
serde_json::from_slice::<Out>(&response_body)
};
parsed_response.map_err(Error::from)
} else {
let error = if response_body.is_empty() {
anyhow!("code: {}, empty response", status)
} else {
anyhow!(
"code: {}, error: {:?}",
status,
String::from_utf8_lossy(&response_body),
)
};
Err(error)
}
}
#[allow(dead_code)]
async fn request_with_accept_mime<Out>(
&self,
method: reqwest::Method,
uri: &str,
accept_mime_type: &str,
) -> Result<Out>
where
Out: serde::de::DeserializeOwned + 'static + Send,
{
let u = if uri.starts_with("https://") {
uri.to_string()
} else {
(self.host.clone() + uri).to_string()
};
let (url, auth) = self.url_and_auth(&u).await?;
let instance = <&Client>::clone(&self);
let mut req = instance.client.request(method, url);
req = req.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_str(accept_mime_type)?,
);
if let Some(auth_str) = auth {
req = req.header(http::header::AUTHORIZATION, &*auth_str);
}
let response = req.send().await?;
let status = response.status();
let response_body = response.bytes().await?;
if status.is_success() {
log::debug!("Received successful response. Read payload.");
let parsed_response = if status == http::StatusCode::NO_CONTENT
|| std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
{
serde_json::from_str("null")
} else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
serde_json::from_value(serde_json::json!(&String::from_utf8(
response_body.to_vec()
)?))
} else {
serde_json::from_slice::<Out>(&response_body)
};
parsed_response.map_err(Error::from)
} else {
let error = if response_body.is_empty() {
anyhow!("code: {}, empty response", status)
} else {
anyhow!(
"code: {}, error: {:?}",
status,
String::from_utf8_lossy(&response_body),
)
};
Err(error)
}
}
#[allow(dead_code)]
async fn request_with_mime<Out>(
&self,
method: reqwest::Method,
uri: &str,
content: &[u8],
mime_type: &str,
) -> Result<Out>
where
Out: serde::de::DeserializeOwned + 'static + Send,
{
let u = if uri.starts_with("https://") {
uri.to_string()
} else {
(self.host.clone() + uri).to_string()
};
let (url, auth) = self.url_and_auth(&u).await?;
let instance = <&Client>::clone(&self);
let mut req = instance.client.request(method, url);
req = req.header(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
req = req.header(
reqwest::header::CONTENT_TYPE,
reqwest::header::HeaderValue::from_bytes(mime_type.as_bytes()).unwrap(),
);
req = req.header(
reqwest::header::HeaderName::from_static("x-upload-content-type"),
reqwest::header::HeaderValue::from_static("application/octet-stream"),
);
req = req.header(
reqwest::header::HeaderName::from_static("x-upload-content-length"),
reqwest::header::HeaderValue::from_bytes(format!("{}", content.len()).as_bytes())
.unwrap(),
);
if let Some(auth_str) = auth {
req = req.header(http::header::AUTHORIZATION, &*auth_str);
}
if content.len() > 1 {
let b = bytes::Bytes::copy_from_slice(content);
req = req.body(b);
}
let response = req.send().await?;
let status = response.status();
let response_body = response.bytes().await?;
if status.is_success() {
log::debug!("Received successful response. Read payload.");
let parsed_response = if status == http::StatusCode::NO_CONTENT
|| std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
{
serde_json::from_str("null")
} else {
serde_json::from_slice::<Out>(&response_body)
};
parsed_response.map_err(Error::from)
} else {
let error = if response_body.is_empty() {
anyhow!("code: {}, empty response", status)
} else {
anyhow!(
"code: {}, error: {:?}",
status,
String::from_utf8_lossy(&response_body),
)
};
Err(error)
}
}
async fn request_entity<D>(
&self,
method: http::Method,
uri: &str,
body: Option<reqwest::Body>,
) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
let r = self.request(method, uri, body).await?;
Ok(r)
}
#[allow(dead_code)]
async fn get<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_entity(http::Method::GET, &(self.host.to_string() + uri), message)
.await
}
#[allow(dead_code)]
async fn get_all_pages<D>(&self, uri: &str, _message: Option<reqwest::Body>) -> Result<Vec<D>>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.unfold(uri).await
}
#[allow(dead_code)]
async fn unfold<D>(&self, uri: &str) -> Result<Vec<D>>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
let mut global_items = Vec::new();
let (new_link, mut items) = self.get_pages(uri).await?;
let mut link = new_link;
while !items.is_empty() {
global_items.append(&mut items);
if let Some(url) = link.as_ref().and_then(crate::utils::next_link) {
let url = reqwest::Url::parse(&url)?;
let (new_link, new_items) = self.get_pages_url(&url).await?;
link = new_link;
items = new_items;
}
}
Ok(global_items)
}
#[allow(dead_code)]
async fn get_pages<D>(&self, uri: &str) -> Result<(Option<hyperx::header::Link>, Vec<D>)>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_with_links(http::Method::GET, &(self.host.to_string() + uri), None)
.await
}
#[allow(dead_code)]
async fn get_pages_url<D>(
&self,
url: &reqwest::Url,
) -> Result<(Option<hyperx::header::Link>, Vec<D>)>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_with_links(http::Method::GET, url.as_str(), None)
.await
}
#[allow(dead_code)]
async fn post<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_entity(http::Method::POST, &(self.host.to_string() + uri), message)
.await
}
#[allow(dead_code)]
async fn patch<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_entity(http::Method::PATCH, &(self.host.to_string() + uri), message)
.await
}
#[allow(dead_code)]
async fn put<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_entity(http::Method::PUT, &(self.host.to_string() + uri), message)
.await
}
#[allow(dead_code)]
async fn delete<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
where
D: serde::de::DeserializeOwned + 'static + Send,
{
self.request_entity(
http::Method::DELETE,
&(self.host.to_string() + uri),
message,
)
.await
}
pub fn admin_apps(&self) -> admin_apps::AdminApps {
admin_apps::AdminApps::new(self.clone())
}
pub fn admin_apps_approved(&self) -> admin_apps_approved::AdminAppsApproved {
admin_apps_approved::AdminAppsApproved::new(self.clone())
}
pub fn admin_apps_requests(&self) -> admin_apps_requests::AdminAppsRequests {
admin_apps_requests::AdminAppsRequests::new(self.clone())
}
pub fn admin_apps_restricted(&self) -> admin_apps_restricted::AdminAppsRestricted {
admin_apps_restricted::AdminAppsRestricted::new(self.clone())
}
pub fn admin_conversations(&self) -> admin_conversations::AdminConversations {
admin_conversations::AdminConversations::new(self.clone())
}
pub fn admin_conversations_ekm(&self) -> admin_conversations_ekm::AdminConversationsEkm {
admin_conversations_ekm::AdminConversationsEkm::new(self.clone())
}
pub fn admin_conversations_restrict_access(
&self,
) -> admin_conversations_restrict_access::AdminConversationsRestrictAccess {
admin_conversations_restrict_access::AdminConversationsRestrictAccess::new(self.clone())
}
pub fn admin_emoji(&self) -> admin_emoji::AdminEmoji {
admin_emoji::AdminEmoji::new(self.clone())
}
pub fn admin_invite_requests(&self) -> admin_invite_requests::AdminInviteRequests {
admin_invite_requests::AdminInviteRequests::new(self.clone())
}
pub fn admin_invite_requests_approved(
&self,
) -> admin_invite_requests_approved::AdminInviteRequestsApproved {
admin_invite_requests_approved::AdminInviteRequestsApproved::new(self.clone())
}
pub fn admin_invite_requests_denied(
&self,
) -> admin_invite_requests_denied::AdminInviteRequestsDenied {
admin_invite_requests_denied::AdminInviteRequestsDenied::new(self.clone())
}
pub fn admin_teams(&self) -> admin_teams::AdminTeams {
admin_teams::AdminTeams::new(self.clone())
}
pub fn admin_teams_admins(&self) -> admin_teams_admins::AdminTeamsAdmins {
admin_teams_admins::AdminTeamsAdmins::new(self.clone())
}
pub fn admin_teams_owners(&self) -> admin_teams_owners::AdminTeamsOwners {
admin_teams_owners::AdminTeamsOwners::new(self.clone())
}
pub fn admin_teams_settings(&self) -> admin_teams_settings::AdminTeamsSettings {
admin_teams_settings::AdminTeamsSettings::new(self.clone())
}
pub fn admin_usergroups(&self) -> admin_usergroups::AdminUsergroups {
admin_usergroups::AdminUsergroups::new(self.clone())
}
pub fn admin_users(&self) -> admin_users::AdminUsers {
admin_users::AdminUsers::new(self.clone())
}
pub fn admin_users_session(&self) -> admin_users_session::AdminUsersSession {
admin_users_session::AdminUsersSession::new(self.clone())
}
pub fn api(&self) -> api::Api {
api::Api::new(self.clone())
}
pub fn apps(&self) -> apps::Apps {
apps::Apps::new(self.clone())
}
pub fn apps_event_authorizations(&self) -> apps_event_authorizations::AppsEventAuthorizations {
apps_event_authorizations::AppsEventAuthorizations::new(self.clone())
}
pub fn apps_permissions(&self) -> apps_permissions::AppsPermissions {
apps_permissions::AppsPermissions::new(self.clone())
}
pub fn apps_permissions_resources(
&self,
) -> apps_permissions_resources::AppsPermissionsResources {
apps_permissions_resources::AppsPermissionsResources::new(self.clone())
}
pub fn apps_permissions_scopes(&self) -> apps_permissions_scopes::AppsPermissionsScopes {
apps_permissions_scopes::AppsPermissionsScopes::new(self.clone())
}
pub fn apps_permissions_users(&self) -> apps_permissions_users::AppsPermissionsUsers {
apps_permissions_users::AppsPermissionsUsers::new(self.clone())
}
pub fn auth(&self) -> auth::Auth {
auth::Auth::new(self.clone())
}
pub fn bots(&self) -> bots::Bots {
bots::Bots::new(self.clone())
}
pub fn calls(&self) -> calls::Calls {
calls::Calls::new(self.clone())
}
pub fn calls_participants(&self) -> calls_participants::CallsParticipants {
calls_participants::CallsParticipants::new(self.clone())
}
pub fn chat(&self) -> chat::Chat {
chat::Chat::new(self.clone())
}
pub fn chat_scheduled_messages(&self) -> chat_scheduled_messages::ChatScheduledMessages {
chat_scheduled_messages::ChatScheduledMessages::new(self.clone())
}
pub fn conversations(&self) -> conversations::Conversations {
conversations::Conversations::new(self.clone())
}
pub fn dialog(&self) -> dialog::Dialog {
dialog::Dialog::new(self.clone())
}
pub fn dnd(&self) -> dnd::Dnd {
dnd::Dnd::new(self.clone())
}
pub fn emoji(&self) -> emoji::Emoji {
emoji::Emoji::new(self.clone())
}
pub fn files(&self) -> files::Files {
files::Files::new(self.clone())
}
pub fn files_comments(&self) -> files_comments::FilesComments {
files_comments::FilesComments::new(self.clone())
}
pub fn files_remote(&self) -> files_remote::FilesRemote {
files_remote::FilesRemote::new(self.clone())
}
pub fn migration(&self) -> migration::Migration {
migration::Migration::new(self.clone())
}
pub fn oauth(&self) -> oauth::Oauth {
oauth::Oauth::new(self.clone())
}
pub fn oauth_v_2(&self) -> oauth_v_2::OauthV2 {
oauth_v_2::OauthV2::new(self.clone())
}
pub fn pins(&self) -> pins::Pins {
pins::Pins::new(self.clone())
}
pub fn reactions(&self) -> reactions::Reactions {
reactions::Reactions::new(self.clone())
}
pub fn reminders(&self) -> reminders::Reminders {
reminders::Reminders::new(self.clone())
}
pub fn rtm(&self) -> rtm::Rtm {
rtm::Rtm::new(self.clone())
}
pub fn search(&self) -> search::Search {
search::Search::new(self.clone())
}
pub fn stars(&self) -> stars::Stars {
stars::Stars::new(self.clone())
}
pub fn team(&self) -> team::Team {
team::Team::new(self.clone())
}
pub fn team_profile(&self) -> team_profile::TeamProfile {
team_profile::TeamProfile::new(self.clone())
}
pub fn usergroups(&self) -> usergroups::Usergroups {
usergroups::Usergroups::new(self.clone())
}
pub fn usergroups_users(&self) -> usergroups_users::UsergroupsUsers {
usergroups_users::UsergroupsUsers::new(self.clone())
}
pub fn users(&self) -> users::Users {
users::Users::new(self.clone())
}
pub fn users_profile(&self) -> users_profile::UsersProfile {
users_profile::UsersProfile::new(self.clone())
}
pub fn views(&self) -> views::Views {
views::Views::new(self.clone())
}
pub fn workflows(&self) -> workflows::Workflows {
workflows::Workflows::new(self.clone())
}
}