wp_mini/endpoints/story.rs
1use crate::client::WattpadRequestBuilder;
2use crate::field::{PartField, StoryField};
3use crate::types::{PartContentResponse, PartResponse, StoryResponse};
4use crate::WattpadError;
5use std::sync::atomic::AtomicBool;
6use std::sync::Arc;
7use bytes::Bytes;
8
9/// Contains methods for story-related API endpoints.
10///
11/// This client provides access to fetching information about stories, story parts,
12/// and their content in various formats.
13pub struct StoryClient {
14 /// The shared `reqwest` client for making HTTP requests.
15 pub(crate) http: reqwest::Client,
16 /// A flag indicating whether the main client is authenticated.
17 pub(crate) is_authenticated: Arc<AtomicBool>,
18}
19
20impl StoryClient {
21 /// Returns detailed information about a story.
22 ///
23 /// # Arguments
24 /// * `story_id` - The unique identifier of the story to fetch.
25 /// * `fields` - An optional slice of `StoryField` specifying which fields to retrieve.
26 /// If `None`, a comprehensive list of all known fields will be requested by default.
27 ///
28 /// # Returns
29 /// A `Result` containing a `StoryResponse` struct with the story's metadata on success.
30 ///
31 /// # Errors
32 /// Returns a `WattpadError` if the network request fails or the API returns an error.
33 ///
34 /// # Examples
35 /// ```no_run
36 /// # use wattpad::{WattpadClient, field::StoryField};
37 /// # #[tokio::main]
38 /// # async fn main() -> Result<(), wattpad::WattpadError> {
39 /// let client = WattpadClient::new();
40 /// let story_id = 12345678; // Example story ID
41 /// let fields = &[StoryField::Title, StoryField::VoteCount];
42 ///
43 /// let story_info = client.story.get_story_info(story_id, Some(fields)).await?;
44 ///
45 /// println!("Title: {}", story_info.title);
46 /// println!("Votes: {}", story_info.vote_count);
47 /// # Ok(())
48 /// # }
49 /// ```
50 pub async fn get_story_info(
51 &self,
52 story_id: u64,
53 fields: Option<&[StoryField]>,
54 ) -> Result<StoryResponse, WattpadError> {
55 WattpadRequestBuilder::new(
56 &self.http,
57 &self.is_authenticated,
58 reqwest::Method::GET,
59 &format!("/api/v3/stories/{}", story_id),
60 )
61 .fields(fields)?
62 .execute()
63 .await
64 }
65
66 /// Returns detailed information about a single story part.
67 ///
68 /// # Arguments
69 /// * `part_id` - The unique identifier of the story part to fetch.
70 /// * `fields` - An optional slice of `PartField` specifying which fields to retrieve.
71 /// If `None`, a default set of fields will be requested.
72 ///
73 /// # Returns
74 /// A `Result` containing a `PartResponse` struct with the part's metadata on success.
75 ///
76 /// # Errors
77 /// Returns a `WattpadError` if the network request fails or the API returns an error.
78 ///
79 /// # Examples
80 /// ```no_run
81 /// # use wattpad::{WattpadClient, field::PartField};
82 /// # #[tokio::main]
83 /// # async fn main() -> Result<(), wattpad::WattpadError> {
84 /// let client = WattpadClient::new();
85 /// let part_id = 87654321; // Example part ID
86 /// let fields = &[PartField::Title, PartField::WordCount];
87 ///
88 /// let part_info = client.story.get_part_info(part_id, Some(fields)).await?;
89 ///
90 /// println!("Part Title: {}", part_info.title);
91 /// println!("Word Count: {}", part_info.word_count);
92 /// # Ok(())
93 /// # }
94 /// ```
95 pub async fn get_part_info(
96 &self,
97 part_id: u64,
98 fields: Option<&[PartField]>,
99 ) -> Result<PartResponse, WattpadError> {
100 WattpadRequestBuilder::new(
101 &self.http,
102 &self.is_authenticated,
103 reqwest::Method::GET,
104 &format!("/api/v3/story_parts/{}", part_id),
105 )
106 .fields(fields)?
107 .execute()
108 .await
109 }
110
111 /// Fetches the raw text content of a single story part.
112 ///
113 /// This endpoint is useful for getting the plain story text without any metadata.
114 ///
115 /// # Arguments
116 /// * `part_id` - The unique identifier for the story part.
117 ///
118 /// # Returns
119 /// A `Result` containing a `String` with the raw story text on success.
120 ///
121 /// # Errors
122 /// Returns a `WattpadError` if the network request fails.
123 ///
124 /// # Examples
125 /// ```no_run
126 /// # use wattpad::WattpadClient;
127 /// # #[tokio::main]
128 /// # async fn main() -> Result<(), wattpad::WattpadError> {
129 /// let client = WattpadClient::new();
130 /// let part_id = 87654321;
131 ///
132 /// let content = client.story.get_part_content_raw(part_id).await?;
133 /// println!("Fetched content snippet: {}...", content.chars().take(100).collect::<String>());
134 /// # Ok(())
135 /// # }
136 /// ```
137 pub async fn get_part_content_raw(&self, part_id: u64) -> Result<String, WattpadError> {
138 WattpadRequestBuilder::new(
139 &self.http,
140 &self.is_authenticated,
141 reqwest::Method::GET,
142 "/apiv2/",
143 )
144 .param("m", Some("storytext"))
145 .param("id", Some(part_id))
146 .execute_raw_text()
147 .await
148 }
149
150 /// Fetches the content of a story part as a structured JSON object.
151 ///
152 /// # Arguments
153 /// * `part_id` - The unique identifier for the story part.
154 ///
155 /// # Returns
156 /// A `Result` containing a `PartContentResponse` struct with the parsed story content on success.
157 ///
158 /// # Errors
159 /// Returns a `WattpadError` if the network request fails or the JSON response cannot be parsed.
160 ///
161 /// # Examples
162 /// ```no_run
163 /// # use wattpad::WattpadClient;
164 /// # #[tokio::main]
165 /// # async fn main() -> Result<(), wattpad::WattpadError> {
166 /// let client = WattpadClient::new();
167 /// let part_id = 87654321;
168 ///
169 /// let content_json = client.story.get_part_content_json(part_id).await?;
170 /// println!("Text from JSON: {}...", content_json.text.chars().take(100).collect::<String>());
171 /// # Ok(())
172 /// # }
173 /// ```
174 pub async fn get_part_content_json(
175 &self,
176 part_id: u64,
177 ) -> Result<PartContentResponse, WattpadError> {
178 WattpadRequestBuilder::new(
179 &self.http,
180 &self.is_authenticated,
181 reqwest::Method::GET,
182 "/apiv2/",
183 )
184 .param("m", Some("storytext"))
185 .param("id", Some(part_id))
186 .param("output", Some("json"))
187 .execute()
188 .await
189 }
190
191 /// Downloads the text content of an entire story as a single ZIP archive.
192 ///
193 /// The archive contains the story text, typically organized by parts.
194 ///
195 /// # Arguments
196 /// * `story_id` - The unique identifier for the story (not a part).
197 ///
198 /// # Returns
199 /// A `Result` containing a `Bytes` object with the binary data of the ZIP file on success.
200 ///
201 /// # Errors
202 /// Returns a `WattpadError` if the network request or download fails.
203 ///
204 /// # Examples
205 /// ```no_run
206 /// # use wattpad::WattpadClient;
207 /// # use std::fs::File;
208 /// # use std::io::Write;
209 /// # #[tokio::main]
210 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
211 /// let client = WattpadClient::new();
212 /// let story_id = 12345678;
213 ///
214 /// let zip_bytes = client.story.get_story_content_zip(story_id).await?;
215 ///
216 /// // Example: Save the ZIP file to disk
217 /// let mut file = File::create(format!("{}.zip", story_id))?;
218 /// file.write_all(&zip_bytes)?;
219 ///
220 /// println!("Successfully downloaded and saved {}.zip", story_id);
221 /// # Ok(())
222 /// # }
223 /// ```
224 pub async fn get_story_content_zip(&self, story_id: u64) -> Result<Bytes, WattpadError> {
225 WattpadRequestBuilder::new(
226 &self.http,
227 &self.is_authenticated,
228 reqwest::Method::GET,
229 "/apiv2/",
230 )
231 .param("m", Some("storytext"))
232 .param("group_id", Some(story_id))
233 .param("output", Some("zip"))
234 .execute_bytes()
235 .await
236 }
237}