1use std::{error::Error, path::Path};
45
46use reqwest::{header::{HeaderMap, HeaderValue, ACCEPT, ACCEPT_LANGUAGE, CACHE_CONTROL, ORIGIN, PRAGMA, REFERER, USER_AGENT}, Client, ClientBuilder};
47use serde::{Deserialize, Serialize};
48use tokio::{fs};
49pub mod error;
50use error::*;
51mod gql;
52mod api;
53use gql::*;
54use api::*;
55
56use crate::{client_type::ClientType, structs::{AvailableDrops, CampaignDetails, ClaimDrop, CurrentDrop, Drops, GameDirectory, GetInventory, PlaybackAccessToken, StreamInfo}};
57pub mod structs;
59pub mod client_type;
61
62#[derive(Deserialize, Serialize, Debug, Default, Clone)]
64pub struct TwitchClient {
65 #[serde(skip)]
66 client: Client,
67 client_id: String,
68 user_agent: String,
69 client_url: String,
70 pub user_id: Option<String>,
71 pub login: Option<String>,
72 pub access_token: Option<String>,
73}
74
75async fn get_headers(
76 client_id: &str,
77 user_agent: &str,
78 access_token: Option<&str>,
79) -> Result<HeaderMap, Box<dyn Error>> {
80 let device_id = uuid::Uuid::new_v4();
81 let mut headers = HeaderMap::new();
82
83 headers.insert(ACCEPT, HeaderValue::from_str("application/json")?);
84 headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_str("en-US")?);
85 headers.insert(CACHE_CONTROL, HeaderValue::from_str("no-cache")?);
86 headers.insert("Client-Id", HeaderValue::from_str(&format!("{}", client_id))?);
87 headers.insert(PRAGMA, HeaderValue::from_str("no-cache")?);
88 headers.insert(ORIGIN, HeaderValue::from_str("https://www.twitch.tv")?);
89 headers.insert(REFERER, HeaderValue::from_str("https://www.twitch.tv")?);
90 headers.insert(USER_AGENT, HeaderValue::from_str(&format!("{}", user_agent))?);
91 headers.insert("X-Device-Id", HeaderValue::from_str(&format!("{}", device_id))?);
92
93 if let Some(token) = access_token {
94 headers.insert("Authorization", HeaderValue::from_str(&format!("OAuth {}", token))?);
95 }
96
97 Ok(headers)
98}
99
100impl TwitchClient {
101 pub async fn save_file(self, path: &Path) -> Result<Self, SystemError> {
104 if !path.exists() {
105 let info = match serde_json::to_string_pretty(&self) {
106 Ok(s) => s,
107 Err(e) => return Err(SystemError::SerializationProblem(e)),
108 };
109 fs::write(&path, info.as_bytes()).await.unwrap();
110 Ok(self)
111 } else {
112 Err(SystemError::FileAlredyExists)
113 }
114 }
115
116 pub async fn load_from_file(path: &Path) -> Result<Self, SystemError> {
119 if !path.exists() {
120 return Err(SystemError::FileNotFound);
121 }
122
123 let load = fs::read_to_string(&path).await.unwrap();
124 let mut load: TwitchClient = match serde_json::from_str(&load) {
125 Ok(s) => s,
126 Err(e) => return Err(SystemError::DeserializationProblem(e)),
127 };
128
129 let headers = get_headers(&load.client_id, &load.user_agent, load.access_token.as_deref()).await?;
130 let client = ClientBuilder::new().default_headers(headers).build()?;
131 load.client = client;
132
133 Ok(load)
134 }
135
136 pub async fn new(client_type: &ClientType) -> Result<Self, SystemError> {
138 let headers = get_headers(&client_type.client_id, &client_type.user_agent, None).await?;
139 let client = ClientBuilder::new().default_headers(headers).build()?;
140
141 Ok(TwitchClient {
142 client,
143 client_id: client_type.client_id.to_string(),
144 user_agent: client_type.user_agent.to_string(),
145 client_url: client_type.client_url.to_string(),
146 user_id: None,
147 login: None,
148 access_token: None,
149 })
150 }
151
152 pub async fn request_device_auth(&self) -> Result<DeviceAuth, TwitchError> {
155 let auth = request_device_auth(&self.client, &self.client_id).await?;
156 Ok(auth)
157 }
158
159 pub async fn auth (&mut self, device_auth: DeviceAuth) -> Result<(), TwitchError> {
162 let auth = poll_device_auth(&self.client, &self.client_id, device_auth).await?;
163 self.access_token = Some(auth.0);
164 self.user_id = Some(auth.1);
165 self.login = Some(auth.2);
166 Ok(())
167 }
168
169 pub async fn send_watch(&self, channel_login: &str, broadcast_id: &str, channel_id: &str) -> Result<(), TwitchError> {
171 if let Some(user_id) = &self.user_id {
172 send_watch(&self.client, &user_id, &self.client_url, channel_login, broadcast_id, channel_id).await?;
173 } else {
174 return Err(TwitchError::TwitchError("Not found user_id".into()));
175 }
176
177 Ok(())
178 }
179
180 pub async fn get_inventory (&self) -> Result<GetInventory, TwitchError> {
184 let inv = inventory(&self.client).await?;
185 Ok(inv)
186 }
187
188 pub async fn get_campaign (&self) -> Result<Drops, TwitchError> {
190 let drops = campaign(&self.client).await?;
191 Ok(drops)
192 }
193
194 pub async fn get_slug (&self, game_name: &str) -> Result<String, TwitchError> {
196 let slug = slug_redirect(&self.client, game_name).await?;
197 Ok(slug)
198 }
199
200 pub async fn get_playback_access_token (&self, channel_login: &str) -> Result<PlaybackAccessToken, TwitchError> {
202 let playback = playback_access_token(&self.client, channel_login).await?;
203 Ok(playback)
204 }
205
206 pub async fn get_game_directory(&self, game_slug: &str, limit: u64, drops_enabled: bool) -> Result<Vec<GameDirectory>, TwitchError> {
208 let streams = game_directory(&self.client, game_slug, limit, drops_enabled).await?;
209 Ok(streams)
210 }
211
212 pub async fn get_available_drops_for_channel (&self, channel_id: &str) -> Result<AvailableDrops, TwitchError> {
214 let drops = available_drops(&self.client, channel_id).await?;
215 Ok(drops)
216 }
217
218 pub async fn get_campaign_details (&self, drop_id: &str) -> Result<CampaignDetails, TwitchError> {
220 if let Some(login) = &self.login {
221 let details = campaign_details(&self.client, &login, drop_id).await?;
222 return Ok(details)
223 } else {
224 return Err(TwitchError::TwitchError("Not found login".into()));
225 }
226 }
227
228 pub async fn get_current_drop_progress_on_channel (&self, channel_login: &str, channel_id: &str) -> Result<CurrentDrop, TwitchError> {
230 let current = current_drop(&self.client, channel_login, channel_id).await?;
231 Ok(current)
232 }
233
234 pub async fn get_stream_info (&self, channel_login: &str) -> Result<StreamInfo, TwitchError> {
236 let stream_info = stream_info(&self.client, channel_login).await?;
237 Ok(stream_info)
238 }
239
240 pub async fn claim_drop (&self, drop_instance_id: &str) -> Result<ClaimDrop, TwitchError> {
242 let claim = claim_drop(&self.client, drop_instance_id).await?;
243 Ok(claim)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[tokio::test]
252 async fn test() -> Result<(), Box<dyn Error>> {
253 Ok(())
254 }
255}