use std::sync::Arc;
use reqwest::{StatusCode, Client, Method, RequestBuilder};
use s2rs_derive::Forwarder;
use crate::{cookies::Cookies, headers, utils::into_arc::IntoArc};
pub use studio::*;
pub use user::*;
pub use project::*;
pub use following_action::*;
pub use studio_action::*;
pub use message::*;
pub use comment::*;
pub use studio_project::*;
pub use user_comment::*;
pub use cloud_action::*;
pub use user_featured::*;
pub use front_page::*;
pub use explore::*;
pub use search::*;
pub use forum::*;
pub use login::*;
pub use stuff::*;
pub mod user;
pub mod project;
pub mod studio;
pub mod following_action;
pub mod message;
pub mod comment;
pub mod studio_project;
pub mod user_comment;
pub mod user_action;
pub mod studio_action;
pub mod cloud_action;
pub mod cloud;
pub mod forum;
pub mod user_featured;
pub mod front_page;
pub mod explore;
pub mod search;
pub mod login;
pub mod stuff;
mod utils;
pub mod protocols {
pub const HTTPS: &str = "https://";
pub const HTTP: &str = "http://";
}
pub mod domains {
pub const API: &str = "api.scratch.mit.edu/";
pub const PROJECTS: &str = "projects.scratch.mit.edu/";
pub const BASE: &str = "scratch.mit.edu/";
pub const CLOUD: &str = "clouddata.scratch.mit.edu/";
pub const UPLOADS: &str = "uploads.scratch.mit.edu/";
}
pub struct Tokens {
pub session: String,
pub x: String,
pub csrf: String,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Forwarder, Debug)]
pub enum Error {
#[forward] Status(StatusCode),
#[forward] Network(reqwest::Error),
#[forward] Parsing(serde_json::Error)
}
#[derive(Debug, Forwarder)]
pub enum WithAuthError {
#[forward] Client(reqwest::Error),
#[forward] Headers(HeadersError),
}
#[derive(Forwarder, Debug)]
pub enum HeadersError {
#[forward] Reqwest(headers::TryIntoReqwestHeadersError),
}
#[derive(Default, Debug)]
pub struct Headers {
pub reqwest: reqwest::header::HeaderMap,
pub local: Arc<headers::Headers>
}
impl TryFrom<Arc<headers::Headers>> for Headers {
type Error = HeadersError;
fn try_from(local: Arc<headers::Headers>) -> std::result::Result<Self, Self::Error> {
Ok(Self {
reqwest: (*local).clone().try_into()?,
local,
})
}
}
pub struct ExtensionPipe {
pub client: Arc<Client>,
pub name: Arc<String>,
pub headers: Arc<headers::Headers>,
}
pub trait Extension {
fn extended(pipe: ExtensionPipe, this: Arc<Api>) -> Arc<Self>;
}
#[derive(Debug)]
pub struct Api {
client: Arc<Client>,
name: Arc<String>,
headers: Headers,
}
impl Api {
pub fn extend<T: Extension>(self: &Arc<Self>) -> Arc<T> {
T::extended(ExtensionPipe {
client: self.client.clone(),
name: self.name.clone(),
headers: self.headers.local.clone()
}, self.clone())
}
pub fn new(name: impl IntoArc<String>) -> Arc<Self> {
Arc::new(Self {
client: Arc::new(Client::new()),
name: name.into_arc(),
headers: Headers::default()
})
}
fn core_headers() -> headers::Headers {
let mut headers = headers::Headers::new();
headers.add("referer", "https://scratch.mit.edu");
headers.add("x-requested-with", "XMLHttpRequest");
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36");
headers
}
pub fn with_auth(name: impl Into<Arc<String>>, tokens: &Tokens) -> std::result::Result<Arc<Self>, WithAuthError> {
let mut cookies = Cookies::default();
cookies.add("scratchcsrftoken", tokens.csrf.as_str());
cookies.add("scratchsessionsid", tokens.session.as_str());
cookies.add("scratchlanguage", "en");
let mut headers = Self::core_headers();
headers.add("cookie", Into::<String>::into(cookies));
headers.add("x-csrftoken", &tokens.csrf);
headers.add("x-token", &tokens.x);
Ok(Arc::new(Self {
client: Arc::new(Client::new()),
name: name.into(),
headers: Arc::new(headers).try_into()?,
}))
}
pub fn name(&self) -> &str {
&self.name
}
fn request(&self, method: Method, url: &str) -> RequestBuilder {
self.client.request(method, url).headers(self.headers.reqwest.clone())
}
fn https_request(&self, method: Method, url: &str) -> RequestBuilder {
self.request(method, &format!["{}{url}", protocols::HTTPS])
}
fn request_api(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}{path}", domains::API])
}
fn get(&self, path: &str) -> RequestBuilder {
self.request_api(Method::GET, path)
}
fn put(&self, path: &str) -> RequestBuilder {
self.request_api(Method::PUT, path)
}
fn post(&self, path: &str) -> RequestBuilder {
self.request_api(Method::POST, path)
}
fn request_base(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}{path}", domains::BASE])
}
fn get_base(&self, path: &str) -> RequestBuilder {
self.request_base(Method::GET, path)
}
fn post_base(&self, path: &str) -> RequestBuilder {
self.request_base(Method::POST, path)
}
fn request_site_api(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}site-api/{path}", domains::BASE])
}
#[allow(unused)]
fn get_site_api(&self, path: &str) -> RequestBuilder {
self.request_site_api(Method::GET, path)
}
fn put_site_api(&self, path: &str) -> RequestBuilder {
self.request_site_api(Method::PUT, path)
}
fn post_site_api(&self, path: &str) -> RequestBuilder {
self.request_site_api(Method::POST, path)
}
fn request_proxy(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}proxy/{path}", domains::API])
}
fn get_proxy(&self, path: &str) -> RequestBuilder {
self.request_proxy(Method::GET, path)
}
fn post_proxy(&self, path: &str) -> RequestBuilder {
self.request_proxy(Method::POST, path)
}
fn put_proxy(&self, path: &str) -> RequestBuilder {
self.request_proxy(Method::PUT, path)
}
fn delete_proxy(&self, path: &str) -> RequestBuilder {
self.request_proxy(Method::DELETE, path)
}
#[allow(unused)]
fn request_cloud(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}{path}", domains::CLOUD])
}
fn get_cloud(&self, path: &str) -> RequestBuilder {
self.request_cloud(Method::GET, path)
}
fn request_uploads(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}{path}", domains::UPLOADS])
}
fn get_uploads(&self, path: &str) -> RequestBuilder {
self.request_uploads(Method::GET, path)
}
fn request_internal_api(&self, method: Method, path: &str) -> RequestBuilder {
self.https_request(method, &format!["{}internalapi/{path}", domains::BASE])
}
fn post_internal_api(&self, path: &str) -> RequestBuilder {
self.request_internal_api(Method::POST, path)
}
}