pub mod args;
use reqwest::{
header::{self, HeaderValue},
redirect::Policy,
StatusCode,
};
use shuttlings::{SubmissionState, SubmissionUpdate};
use tokio::{
sync::mpsc::Sender,
time::{sleep, Duration},
};
use tracing::info;
use uuid::Uuid;
pub const SUPPORTED_CHALLENGES: &[&str] = &[
"-1",
];
pub const SUBMISSION_TIMEOUT: u64 = 60;
pub async fn run(url: String, id: Uuid, number: &str, tx: Sender<SubmissionUpdate>) {
info!(%id, %url, %number, "Starting submission");
tx.send(SubmissionState::Running.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
tokio::select! {
_ = validate(url.as_str(), number, tx.clone()) => (),
_ = sleep(Duration::from_secs(SUBMISSION_TIMEOUT)) => {
info!(%id, %url, %number, "Submission timed out");
tx.send("Timed out".to_owned().into()).await.unwrap();
tx.send(SubmissionState::Done.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
},
};
info!(%id, %url, %number, "Completed submission");
}
type TaskTest = (i32, i32);
type ValidateResult = std::result::Result<(), TaskTest>;
pub async fn validate(url: &str, number: &str, tx: Sender<SubmissionUpdate>) {
let txc = tx.clone();
if let Err((task, test)) = match number {
"-1" => validate_minus1(url, txc).await,
_ => {
tx.send(
format!("Validating Challenge {number} is not supported yet! Check for updates.")
.into(),
)
.await
.unwrap();
return;
}
} {
info!(%url, %number, %task, %test, "Submission failed");
tx.send(format!("Task {task}: test #{test} failed 🟥").into())
.await
.unwrap();
}
tx.send(SubmissionState::Done.into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
}
fn new_client() -> reqwest::Client {
reqwest::ClientBuilder::new()
.http1_only()
.connect_timeout(Duration::from_secs(3))
.redirect(Policy::limited(3))
.referer(false)
.timeout(Duration::from_secs(60))
.build()
.unwrap()
}
async fn validate_minus1(base_url: &str, tx: Sender<SubmissionUpdate>) -> ValidateResult {
let client = new_client();
let mut test: TaskTest;
test = (1, 1);
let url = &format!("{}/", base_url);
let res = client.get(url).send().await.map_err(|_| test)?;
if res.status() != StatusCode::OK {
return Err(test);
}
if res.text().await.map_err(|_| test)? != "Hello, bird!" {
return Err(test);
}
tx.send((true, 0).into()).await.unwrap();
tx.send(SubmissionUpdate::Save).await.unwrap();
test = (2, 1);
let url = &format!("{}/-1/seek", base_url);
let client_no_redir = reqwest::ClientBuilder::new()
.http1_only()
.connect_timeout(Duration::from_secs(3))
.redirect(Policy::none())
.referer(false)
.timeout(Duration::from_secs(60))
.build()
.unwrap();
let res = client_no_redir.get(url).send().await.map_err(|_| test)?;
if res.status() != StatusCode::FOUND {
return Err(test);
}
if res.headers().get(header::LOCATION)
!= Some(&HeaderValue::from_static(
"https://www.youtube.com/watch?v=9Gc4QTqslN4",
))
{
return Err(test);
}
tx.send((false, 0).into()).await.unwrap();
Ok(())
}