-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(#22) - Created internal ReAPI Library
* started work on ReAPI * more work * added users to ReAPI * login works * donkey * Fixed ReAPI impl in sb * moved login in base of ReAPI * added debug to login * something * something * Beginnings of SB logic * fix images * remove login from root * More progress * got images to work * Adding ReAPI rooms function * everything * everything * fix merge conflict * fix test * it works * this doesn't work * Batching Messages * reimpl exporting * Adding other export formats; fixing timestamp; image saving only impl. on .txt * Now with 100% more export choises (that actually work) * Fixing shit * Prepare for v1.0.0 release * Fix edition typo * Remove unessesary crates * Removing OneElectrons implicit panic from the test... --------- Co-authored-by: Electron <one.electron109@protonmail.com>
- Loading branch information
1 parent
8cdd87c
commit be06e6d
Showing
19 changed files
with
737 additions
and
595 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
use super::Client; | ||
use crate::exit; | ||
use cached::SizedCache; | ||
use console::style; | ||
use serde::Serialize; | ||
use std::path::PathBuf; | ||
|
||
#[derive(std::hash::Hash, Clone, Debug, Serialize)] | ||
pub struct Image { | ||
pub extension: String, | ||
pub id: String, | ||
pub data: Vec<u8>, | ||
} | ||
|
||
impl Image { | ||
pub fn export_to(&self, path: PathBuf) { | ||
let mut path = path; | ||
path.push(self.id.clone()); | ||
|
||
std::fs::write( | ||
path.with_extension(self.extension.clone()), | ||
self.data.clone(), | ||
) | ||
.unwrap(); | ||
} | ||
|
||
pub fn from(id: String, extension: String, data: Vec<u8>) -> Image { | ||
Image { | ||
extension, | ||
id, | ||
data, | ||
} | ||
} | ||
} | ||
|
||
/// Gets images from a mxc:// URL as per [SPEC](https://spec.matrix.org/v1.6/client-server-api/#get_matrixmediav3downloadservernamemediaid) | ||
#[cached::proc_macro::cached( | ||
type = "SizedCache<String, Image>", | ||
create = "{ SizedCache::with_size(10_000) }", | ||
convert = r#"{ format!("{}", url) }"# | ||
)] | ||
pub fn get_image(client: &Client, url: String) -> Image { | ||
info!(target: "get_image", "Getting image: {}", url); | ||
let (url, id) = parse_matrix_image_url(url.as_str()); | ||
|
||
let data = client.reqwest_client.get(url).send().unwrap(); | ||
|
||
Image { | ||
extension: get_image_extension(&data.headers()), | ||
id, | ||
data: data.bytes().unwrap().to_vec(), | ||
} | ||
} | ||
|
||
fn parse_matrix_image_url(url: &str) -> (String, String) { | ||
let url = reqwest::Url::parse(url).unwrap(); // I assume that all urls given to this function are valid | ||
|
||
let output_url = | ||
reqwest::Url::parse("https://matrix.redditspace.com/_matrix/media/r0/download/reddit.com/") | ||
.unwrap(); | ||
|
||
let id = url.path_segments().unwrap().next().unwrap(); | ||
|
||
let output_url = output_url.join(id).unwrap(); | ||
|
||
(output_url.to_string(), id.to_string()) | ||
} | ||
|
||
fn get_image_extension(headers: &reqwest::header::HeaderMap) -> String { | ||
let mut extension: Option<String> = None; | ||
|
||
// Iterate over headers to find content-type | ||
for (header_name, header_value) in headers { | ||
if header_name.as_str() != "content-type" { | ||
continue; | ||
} | ||
let file_type = header_value.to_str().unwrap().to_string(); | ||
|
||
let mut file_type = file_type.split("/"); | ||
|
||
extension = match file_type.nth(1).unwrap() { | ||
"jpeg" => Some("jpeg".to_string()), | ||
"png" => Some("png".to_string()), | ||
"gif" => Some("gif".to_string()), | ||
_ => { | ||
println!("{}", style("Failed to read image type").red().bold()); | ||
exit!(0); | ||
} | ||
}; | ||
} | ||
|
||
if extension.is_none() { | ||
println!( | ||
"{}", | ||
style("Error: Something failed reading the image type") | ||
.red() | ||
.bold() | ||
); | ||
error!("Something failed reading the image type"); | ||
exit!(0); | ||
} | ||
|
||
return extension.unwrap(); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
fn get_image() { | ||
let image = super::get_image( | ||
&super::super::new_client(true), | ||
"mxc://reddit.com/dwdprq7pxbva1/".to_string(), | ||
); | ||
|
||
image.export_to(std::path::PathBuf::from( | ||
"./test_resources/test_cases/ReAPI/images/get_images/", | ||
)); | ||
|
||
assert!(std::path::PathBuf::from( | ||
"./test_resources/test_cases/ReAPI/images/get_images/dwdprq7pxbva1.gif" | ||
) | ||
.exists()); | ||
|
||
std::fs::remove_file( | ||
"./test_resources/test_cases/ReAPI/images/get_images/dwdprq7pxbva1.gif", | ||
) | ||
.expect("Could not remove downloaded file"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
use console::style; | ||
use regex::Regex; | ||
|
||
impl super::Client { | ||
pub fn logged_in(&self) -> bool { | ||
self.bearer.is_some() | ||
} | ||
|
||
pub fn bearer_token(&self) -> String { | ||
if let Some(token) = self.bearer.clone() { | ||
return token.clone(); | ||
} | ||
|
||
println!("{}", style("You are not logged in").red().bold()); | ||
crate::exit!(0); | ||
} | ||
|
||
pub fn login_with_token(&mut self, bearer: String) { | ||
self.bearer = Some(bearer); | ||
} | ||
|
||
/// Log into Reddit returning the Bearer | ||
pub fn login(&mut self, username: String, password: String) { | ||
// URL encode the password & username | ||
let encoded_password: String; | ||
let username = urlencoding::encode(&username); | ||
|
||
// Reddit is doing a weird thing where * is not urlencoded. Sorry for everyone that has * and %2A in their password | ||
if password.contains("*") { | ||
debug!("Password has *; URL-encode was rewritten"); | ||
encoded_password = password.replace("%2A", "*"); | ||
} else { | ||
encoded_password = urlencoding::encode(&password).into_owned(); | ||
} | ||
|
||
// Send an HTTP GET request to get the CSRF token | ||
let resp = self | ||
.reqwest_client | ||
.get("https://www.reddit.com/login/") | ||
.send() | ||
.expect("Failed to send HTTP request; to obtain CSRF token"); | ||
|
||
debug!("CSRF Request Response: {:?}", resp); | ||
let body = resp.text(); | ||
let body = body.expect("Failed to read response body"); | ||
|
||
// Regex to find the CSRF token in the body of the HTML | ||
let csrf = | ||
Regex::new(r#"<input\s+type="hidden"\s+name="csrf_token"\s+value="([^"]*)""#).unwrap(); | ||
|
||
// For the love of god do not touch this code ever; i made a deal with the devil to make this work | ||
let mut csrf_token: String = String::default(); | ||
for i in csrf.captures_iter(body.as_str()) { | ||
for i in i.get(1).iter() { | ||
csrf_token = String::from(i.as_str().clone()); | ||
debug!("CSRF Token: {}", csrf_token); | ||
} | ||
} | ||
|
||
// Form data for actual login | ||
let form_data = format!( | ||
"csrf_token={}&otp=&password={}&dest=https%3A%2F%2Fwww.reddit.com&username={}", | ||
csrf_token, encoded_password, username | ||
); | ||
|
||
// Perform the actual login post request | ||
let _x = self.reqwest_client | ||
.post("https://www.reddit.com/login") | ||
.header("Content-Type", "application/x-www-form-urlencoded") | ||
.header("Sec-Ch-Ua", "\"Not:A-Brand\";v=\"99\", \"Chromium\";v=\"112\"") | ||
.header("Sec-Ch-Ua-Platform", "Windows") | ||
.header("Sec-Ch-Ua-Mobile", "?0") | ||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.121 Safari/537.36") | ||
.header("Origin", "https://www.reddit.com") | ||
.header("Sec-Fetch-Site", "same-origin") | ||
.header("Sec-Fetch-Mode", "cors") | ||
.header("Sec-Fetch-Dest", "empty") | ||
.header("Referrer","https://www.reddit.com/login/") | ||
.header("Accept-Encoding", "gzip, deflate") | ||
.header("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") | ||
.body(form_data) | ||
.send() | ||
.expect("Failed to send HTTP request; to obtain session token"); | ||
|
||
|
||
// Request / to get the bearer token | ||
let response = self.reqwest_client | ||
.get("https://www.reddit.com/") | ||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.121 Safari/537.36") | ||
.header("Accept-Encoding", "gzip, deflate") | ||
.header("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") | ||
.header("Referrer","https://www.reddit.com/login/") | ||
.header("Sec-Fetch-Dest", "document") | ||
.header("Sec-Fetch-Mode", "navigate") | ||
.header("Sec-Fetch-Site", "same-origin") | ||
.header("Sec-Fetch-User", "?1") | ||
.header("Te", "trailers") | ||
.send() | ||
.expect("Error getting bearer token"); | ||
|
||
// Extract the Bearer Token from the JSON response | ||
let bearer_regex = Regex::new(r#"accessToken":"([^"]+)"#).unwrap(); | ||
|
||
let mut bearer_token: String = String::default(); | ||
for i in bearer_regex.captures_iter(&response.text().unwrap()) { | ||
for i in i.get(1).iter() { | ||
bearer_token = String::from(i.as_str().clone()); | ||
debug!("Bearer Token: {}", bearer_token.trim()); | ||
} | ||
} | ||
|
||
// Login to matrix.reddit.com using the bearer for reddit.com | ||
let data = format!( | ||
"{{\"type\":\"com.reddit.token\",\"token\":\"{bearer_token}\",\"initial_device_display_name\":\"Reddit Web Client\"}}" | ||
); | ||
|
||
debug!("Matrix request body: {:#?}", data); | ||
|
||
let response = self.reqwest_client | ||
.post("https://matrix.redditspace.com/_matrix/client/r0/login") | ||
.header("Content-Type", "application/json") | ||
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.121 Safari/537.36") | ||
.header("Accept", "application/json") | ||
.header("Origin", "https://chat.reddit.com") | ||
.header("Sec-Fetch-Site", "cross-site") | ||
.header("Sec-Fetch-Mode", "cors") | ||
.header("Sec-Fetch-Dest", "empty") | ||
.header("Accept-Encoding", "gzip, deflate") | ||
.header("Accept-Language", "en-US,en;q=0.5") | ||
.header("Te", "trailers") | ||
.body(data) | ||
.send() | ||
.expect("Failed to send HTTP request; to login to matrix"); | ||
|
||
debug!("Matrix login response: {:#?}", response); | ||
if !response.status().is_success() { | ||
println!("{}", style("Login failed").red().bold()); | ||
crate::exit!(0, "Login exited with failure"); | ||
} | ||
|
||
self.bearer = Some(bearer_token); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
#[ignore = "creds"] | ||
fn login() { | ||
let mut client = super::super::new_client(true); | ||
let (username, password) = get_login(); | ||
|
||
client.login(username, password); | ||
} | ||
|
||
fn get_login() -> (String, String) { | ||
let username = std::env::var("REXIT_USERNAME").expect("Could not find username in env"); | ||
let password = std::env::var("REXIT_PASSWORD").expect("Could not find password in env"); | ||
|
||
(username, password) | ||
} | ||
} |
Oops, something went wrong.