1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/gstavrinos/send-to-kindle/master/media/send-to-kindle256.png",
3 html_favicon_url = "https://raw.githubusercontent.com/gstavrinos/send-to-kindle/master/media/send-to-kindle128.png"
4)]
5
6use thirtyfour::prelude::{WebDriverResult, By};
38use thirtyfour::{FirefoxCapabilities, WebDriver};
39use thirtyfour::common::capabilities::firefox::FirefoxPreferences;
40
41#[derive(clap::Parser, Debug)]
42#[command(author, version, about, long_about = None)]
43pub struct Args {
44 #[arg(short, long)]
46 pub username: String,
47
48 #[arg(short, long)]
50 pub password: String,
51
52 #[arg(short, long, default_value_t = String::from(""))]
54 pub file: String,
55
56 #[arg(short, long, default_value_t = String::from(""))]
58 pub directory: String,
59
60 #[arg(short, long, default_value_t = String::from(""))]
62 pub extension: String,
63
64 #[arg(long, default_value_t = 60)]
66 pub file_timeout: usize,
67
68 #[arg(long, default_value_t = true)]
70 pub geckodriver_daemon: bool,
71
72 #[arg(long, default_value_t = false)]
74 pub debugging_mode: bool,
75
76 #[arg(long, default_value_t = String::from("https://www.amazon.com/sendtokindle"))]
78 pub amazon_url: String,
79
80}
81
82impl Default for Args {
83 fn default() -> Args {
84 Args {
85 username: String::from(""),
86 password: String::from(""),
87 file: String::from(""),
88 directory: String::from(""),
89 extension: String::from(""),
90 file_timeout: 60,
91 geckodriver_daemon: true,
92 debugging_mode: false,
93 amazon_url: String::from("https://www.amazon.com/sendtokindle"),
94 }
95 }
96}
97
98impl Args {
99 pub fn new(u: &str, p: &str) -> Args {
100 let mut args = Args::default();
101 args.username = String::from(u);
102 args.password = String::from(p);
103 return args;
104 }
105}
106
107pub async fn send_files_to_kindle(username: &str, password: &str, files: Vec<String>, file_timeout: usize, url: &str, daemon: bool, debugging_mode: bool) -> WebDriverResult<()> {
120 let mut gd_daemon = std::process::Command::new("echo").stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).spawn()?;
121 if daemon {
122 gd_daemon = std::process::Command::new("geckodriver").stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).spawn()?;
123 }
124 let user_agent = "Linux";
125
126 let mut prefs = FirefoxPreferences::new();
127 prefs.set_user_agent(user_agent.to_string())?;
128
129 let mut caps = FirefoxCapabilities::new();
130 caps.set_preferences(prefs)?;
131 if !debugging_mode {
132 caps.set_headless()?;
133 }
134
135 let driver = WebDriver::new("http://localhost:4444", caps).await?;
136 driver.goto(url).await?;
137 println!("Reached {}", url);
138
139 let signin_button = driver.find(By::Id("s2k-dnd-sign-in-button")).await?;
140 signin_button.click().await?;
141 println!("Found sign in button");
142
143 let email_input = driver.find(By::Css("input[type='email']")).await?;
144 email_input.send_keys(username).await?;
145 println!("Found email input and sent user email");
146 let continue_button = driver.find(By::Css("input[type='submit'][id='continue']")).await?;
147 continue_button.click().await?;
148 println!("Found and clicked continue button");
149 let password_input = driver.find(By::Css("input[type='password'][id='ap_password']")).await?;
150 password_input.send_keys(password).await?;
151 println!("Found password input and sent user password");
152 let sis_button = driver.find(By::Css("input[type='submit'][id='signInSubmit']")).await?;
153 sis_button.click().await?;
154 println!("Found and clicked sign in button");
155 driver.execute(r#"
156
157 let elem = document.getElementById("s2k-home-wrapper");
158
159 var input = document.createElement("input");
160 input.id = "hacky-file-input";
161 input.type = "file";
162 input.multiple = true;
163 elem.appendChild(input);
164 "#, Vec::new()).await?;
165
166 let file_input = driver.find(By::Id("hacky-file-input")).await?;
167 for f in files.clone() {
168 file_input.send_keys(f).await?;
169 }
170 driver.execute(r#"
171 let elem = document.getElementById("s2k-home-wrapper");
172
173 function CustomDataTransfer() {
174 var f = document.getElementById("hacky-file-input").files;
175 this.dropEffect = 'all';
176 this.effectAllowed = 'all';
177 this.items = [];
178 this.types = ['Files'];
179 this.files = f;
180 };
181
182 var customDropEvent = new DragEvent('drop');
183 Object.defineProperty(customDropEvent, 'dataTransfer', {
184 value: new CustomDataTransfer()
185 });
186 var button_input = document.createElement("button");
187 button_input.id = "hacky-button-file-input";
188 button_input.addEventListener('click', function(e) {
189 e.preventDefault();
190
191 // the fake event will be called on the button click
192 document.getElementById("s2k-dnd-area").dispatchEvent(customDropEvent);
193 });
194 elem.appendChild(button_input); // put it into the DOM
195
196 "#, Vec::new()
197 ).await?;
198 let dnd_area = driver.find(By::Id("s2k-dnd-area")).await?;
199 if !dnd_area.is_displayed().await? {
200 println!("Something's off with the dnd area...");
201 }
202 else {
203 println!("Found dnd area, we are ready to send some files!");
204 }
205 let file_input_button = driver.find(By::Id("hacky-button-file-input")).await?;
206 file_input_button.click().await?;
207 driver.find(By::Css(".s2k-r2s-file-item")).await?;
208 println!("Found at least one item in the dnd area");
209 let add_to_library_label = driver.find(By::Id("s2k-r2s-add2lib")).await?;
210 let add_to_library_checkbox = add_to_library_label.find(By::Css("input[type='checkbox']")).await?;
211 if add_to_library_checkbox.prop("checked").await?.unwrap() != "true" {
212 add_to_library_label.click().await?;
213 }
214 println!("Found the 'Add to your library' checkbox, and ensured that it is checked");
215 if !debugging_mode {
216 let start = std::time::Instant::now();
217 let send_button = driver.find(By::Id("s2k-r2s-send-button")).await?;
218 send_button.click().await?;
219 println!("Found and clicked the send button");
220 let mut uploading = true;
221 println!("Waiting for files to upload...");
222 while uploading {
223 uploading = !dnd_area.is_displayed().await?;
224 if start.elapsed().as_secs() as usize > files.clone().len() * file_timeout {
225 println!("Waited for more than {} seconds per file and still appears that not everything was done. Giving up. (If your connection is slow, try increasing the --file-timeout argument)", file_timeout);
226 break;
227 }
228 std::thread::sleep(std::time::Duration::from_secs(3));
229 print!(".");
230 }
231 if !uploading {
232 println!("\nEverything uploaded successfully! :)");
233 }
234 }
235 else {
236 println!("DEBUGGING MODE: Press enter to close the browser window...");
237 let mut _s = String::new();
238 std::io::stdin().read_line(&mut _s)?;
239 }
240 driver.close_window().await?;
241 if daemon {
242 gd_daemon.kill()?;
243 }
244 Ok(())
245}
246
247pub async fn send_to_kindle(username: &str, password: &str, f: &str, ext: &str, file_timeout: usize, url: &str, daemon: bool, debugging_mode: bool) -> WebDriverResult<()> {
261 let mut files = Vec::<String>::new();
262 let source = std::path::Path::new(f);
263 if source.is_file() {
264 if ext == "" || ext == source.extension().unwrap_or(std::ffi::OsStr::new("")) {
265 files.push(String::from(f));
266 }
267 }
268 else if source.is_dir() {
269 for file in std::fs::read_dir(f).unwrap() {
270 let file_path = file.unwrap().path();
271 if file_path.ends_with(ext) {
272 files.push(file_path.display().to_string());
273 }
274 }
275 }
276 send_files_to_kindle(username, password, files, file_timeout, url, daemon, debugging_mode).await
277}
278