use crate::{
access_token, AccessTokenProvider, ApiError, AuthenticatedRequestError, ChannelInfo,
ChannelUpdate, ChannelUpdatePayload, ClientIdProvider, EmoteChannels, EmoteFetchType,
ErrorStatus, GetChannelByIdPayload, GetEmotesPayload, GetEmotesResponse, GetUsersPayload,
GetUsersResponse, RequestError, User,
};
use reqwest::header;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Client<A> {
pub(crate) http: reqwest::Client,
pub(crate) auth_provider: A,
}
impl<A> Client<A> {
pub fn new(auth_provider: A) -> Self {
Self {
http: reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()
.unwrap(),
auth_provider,
}
}
pub fn from_reqwest(http: reqwest::Client, auth_provider: A) -> Self {
Self {
http,
auth_provider,
}
}
}
impl<A> Client<A>
where
A: ClientIdProvider,
{
pub async fn users(&self, usernames: Vec<String>) -> Result<Vec<User>, RequestError> {
let res = self
.http
.post("https://open-api.trovo.live/openplatform/getusers")
.header("Client-ID", self.auth_provider.client_id())
.json(&GetUsersPayload { user: usernames })
.send()
.await?;
if ApiError::can_handle_code(res.status()) {
let err: ApiError = res.json().await.unwrap_or_default();
if err.status == ErrorStatus::InvalidParameters {
return Ok(vec![]);
} else {
return Err(RequestError::ApiError(err));
}
}
let response: GetUsersResponse = res.error_for_status()?.json().await?;
Ok(response.users)
}
pub async fn user(&self, username: impl Into<String>) -> Result<Option<User>, RequestError> {
let mut users = self.users(vec![username.into()]).await?;
if !users.is_empty() {
Ok(Some(users.remove(0)))
} else {
Ok(None)
}
}
pub async fn channel_by_id(
&self,
channel_id: impl Into<String>,
) -> Result<Option<ChannelInfo>, RequestError> {
let res = self
.http
.post("https://open-api.trovo.live/openplatform/channels/id")
.header("Client-ID", self.auth_provider.client_id())
.json(&GetChannelByIdPayload {
channel_id: channel_id.into(),
})
.send()
.await?;
if ApiError::can_handle_code(res.status()) {
let err: ApiError = res.json().await.unwrap_or_default();
return Err(RequestError::ApiError(err));
}
let channel: ChannelInfo = res.error_for_status()?.json().await?;
Ok(if channel.username.is_empty() {
None
} else {
Some(channel)
})
}
pub async fn emotes(
&self,
emote_type: EmoteFetchType,
channel_ids: Vec<String>,
) -> Result<EmoteChannels, RequestError> {
let res = self
.http
.post("https://open-api.trovo.live/openplatform/getemotes")
.header("Client-ID", self.auth_provider.client_id())
.json(&GetEmotesPayload {
emote_type,
channel_id: channel_ids,
})
.send()
.await?;
if ApiError::can_handle_code(res.status()) {
let err: ApiError = res.json().await.unwrap_or_default();
return Err(RequestError::ApiError(err));
}
let response: GetEmotesResponse = res.error_for_status()?.json().await?;
Ok(response.channels)
}
}
impl<A> Client<A>
where
A: AccessTokenProvider,
{
pub async fn update_channel(
&self,
channel_id: impl Into<String>,
update: ChannelUpdate,
) -> Result<(), AuthenticatedRequestError<A::Error>> {
let res = self
.http
.post("https://open-api.trovo.live/openplatform/channels/update")
.header("Client-ID", self.auth_provider.client_id())
.header(
header::AUTHORIZATION,
format!(
"OAuth {}",
access_token!(self.auth_provider, AuthenticatedRequestError)
),
)
.json(&ChannelUpdatePayload {
channel_id: channel_id.into(),
update,
})
.send()
.await?;
if ApiError::can_handle_code(res.status()) {
let err: ApiError = res.json().await.unwrap_or_default();
Err(AuthenticatedRequestError::ApiError(err))
} else {
res.error_for_status()?;
Ok(())
}
}
}