initial commit

This commit is contained in:
aaron-jack-manning
2024-03-01 21:28:01 +11:00
commit d2978bb1c7
6 changed files with 1934 additions and 0 deletions

127
src/main.rs Normal file
View File

@@ -0,0 +1,127 @@
use std::fs;
use std::path;
use std::io::Read;
use anyhow::Context;
#[derive(clap::Parser)]
struct Args {
#[clap(long, short)]
username : Option<String>,
#[clap(long, short)]
password : Option<String>,
#[clap(long, short)]
output : path::PathBuf,
}
#[derive(serde::Serialize)]
struct Login<'a> {
#[serde(rename = "__csrf")]
csrf : &'a str,
#[serde(rename = "authenticationCode")]
authentication_code : &'a str,
username : &'a str,
password : &'a str,
}
#[derive(serde::Deserialize)]
#[allow(dead_code)]
struct LoginResult {
result : String,
messages : Vec<String>,
csrf : String,
}
fn main() -> anyhow::Result<()> {
let args : Args = clap::Parser::parse();
let client = reqwest::blocking::ClientBuilder::new()
// CSRF cookies and login cookies must be passed between requests for authentication to
// succeed
.cookie_store(true)
.build()?;
let home_response = client.get("https://letterboxd.com")
.send()?;
let home_cookies = home_response.headers()
.get("set-cookie")
.context("did not recieve csrf cookie when making home page request")?
.to_str()
.context("cookie value could not be read as string")?;
let cookie = cookie::Cookie::parse(home_cookies).context("failed to parse cookie value")?;
let csrf = if cookie.name() == "com.xk72.webparts.csrf" {
cookie.value()
} else {
anyhow::bail!("unexpected cookie from home page request")
};
let login = Login {
username : &match args.username {
Some(username) => username,
None => {
dialoguer::Input::new()
.with_prompt("Username")
.interact_text()
.unwrap()
}
},
password : &match args.password {
Some(password) => password,
None => {
dialoguer::Password::new()
.with_prompt("Password")
.interact()
.context("failed to read password")?
}
},
authentication_code : "",
csrf,
};
let login = serde_urlencoded::to_string(&login)
.context("issue with login request serialization")?;
let mut login_response = client.post("https://letterboxd.com/user/login.do")
.body(login)
.header("Content-Type", "application/x-www-form-urlencoded")
.send()?;
{
let mut buffer = String::new();
login_response.read_to_string(&mut buffer)?;
let login_response = serde_json::from_str::<LoginResult>(buffer.as_str())?;
if login_response.result != "success" {
anyhow::bail!("login failed")
}
}
let mut export_response = client.get("https://letterboxd.com/data/export/")
.send()?;
if export_response.status() != reqwest::StatusCode::OK {
anyhow::bail!("non 200 status code returned from data export")
}
let mut zip = export_response.headers().get("Content-Length")
.and_then(|val| {
val.to_str().ok()
})
.and_then(|val| {
str::parse::<usize>(val).ok()
})
.map(|len| {
Vec::with_capacity(len)
}).unwrap_or(Vec::new());
export_response.read_to_end(&mut zip)?;
fs::write(args.output, zip)?;
Ok(())
}