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}