feat: get courses
This commit is contained in:
parent
3cbc26e11c
commit
9d247c76d5
21 changed files with 2604 additions and 255 deletions
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[submodule "crates/tui-confirm-dialog"]
|
||||
path = crates/tui-confirm-dialog
|
||||
url = https://git.newty.dev/newt/tui-confirm-dialog-tokio
|
||||
[submodule "crates/reqwest_cookie_store_tokio"]
|
||||
path = crates/reqwest_cookie_store_tokio
|
||||
url = https://git.newty.dev/newt/reqwest_cookie_store_tokio
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"rust-analyzer.cargo.features": "all"
|
||||
"files.exclude": {
|
||||
"target": true
|
||||
}
|
||||
}
|
||||
|
|
134
Cargo.lock
generated
134
Cargo.lock
generated
|
@ -28,9 +28,31 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
|
@ -117,9 +139,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.31"
|
||||
version = "1.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
||||
checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -241,6 +263,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
|
@ -279,6 +311,7 @@ dependencies = [
|
|||
"cookie_store",
|
||||
"crossterm",
|
||||
"email_address",
|
||||
"ijson",
|
||||
"lazy_static",
|
||||
"paste",
|
||||
"ratatui",
|
||||
|
@ -345,9 +378,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
|
@ -506,9 +539,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
|
@ -788,6 +821,18 @@ dependencies = [
|
|||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ijson"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96214564d1f12875bd9661b183d8494dd10e373cb693629536fe2f3125e254b"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
|
@ -858,9 +903,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
@ -972,6 +1017,16 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
|
@ -1223,9 +1278,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -1287,13 +1342,15 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reqwest_cookie_store"
|
||||
version = "0.8.0"
|
||||
source = "git+https://git.newty.dev/newt/reqwest_cookie_store_tokio.git#aad0c697e209c6e79b15ff49b3bf0d05663492c3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie_store",
|
||||
"futures",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -1320,9 +1377,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.38"
|
||||
version = "0.38.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
@ -1412,9 +1469,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
|
||||
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
|
@ -1592,9 +1649,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.86"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1644,9 +1701,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
|
@ -1657,18 +1714,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.66"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.66"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1791,6 +1848,30 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-test"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.12"
|
||||
|
@ -1871,13 +1952,12 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "tui_confirm_dialog"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117929c8e294f8ceea4f56f4d09a6a4e694f057d175f2cf61433309fb01e9a94"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"rand",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -1,24 +1,4 @@
|
|||
[package]
|
||||
name = "echoed"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.83"
|
||||
color-eyre = "0.6.3"
|
||||
cookie_store = "0.21.1"
|
||||
crossterm = "0.28.1"
|
||||
email_address = "0.2.9"
|
||||
lazy_static = "1.5.0"
|
||||
paste = "1.0.15"
|
||||
ratatui = "0.29.0"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.9", features = ["cookies", "multipart"] }
|
||||
reqwest_cookie_store = { git = "https://git.newty.dev/newt/reqwest_cookie_store_tokio.git" }
|
||||
serde_cbor = "0.11.2"
|
||||
thiserror = "1.0.66"
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
|
||||
tui-textarea = "0.7.0"
|
||||
tui_confirm_dialog = "0.2.4"
|
||||
url = "2.5.3"
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
default-members = ["crates/echoed"]
|
||||
members = ["crates/*"]
|
||||
|
|
2349
crates/echoed/Cargo.lock
generated
Normal file
2349
crates/echoed/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
25
crates/echoed/Cargo.toml
Normal file
25
crates/echoed/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "echoed"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.83"
|
||||
color-eyre = "0.6.3"
|
||||
cookie_store = "0.21.1"
|
||||
crossterm = "0.28.1"
|
||||
email_address = "0.2.9"
|
||||
ijson = "0.1.3"
|
||||
lazy_static = "1.5.0"
|
||||
paste = "1.0.15"
|
||||
ratatui = "0.29.0"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.9", features = ["cookies", "json", "multipart"] }
|
||||
reqwest_cookie_store = { path = "../reqwest_cookie_store_tokio" }
|
||||
serde_cbor = "0.11.2"
|
||||
thiserror = "1.0.66"
|
||||
tokio = { version = "1.41.1", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||
tui-textarea = "0.7.0"
|
||||
tui_confirm_dialog = { path = "../tui-confirm-dialog" }
|
||||
url = "2.5.3"
|
|
@ -2,7 +2,6 @@ use color_eyre::Result;
|
|||
use echoed::{UserData, DEFAULT_ECHO360};
|
||||
use regex::Regex;
|
||||
use reqwest::multipart::Form;
|
||||
use reqwest_cookie_store::CookieStoreMutex;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::State;
|
|
@ -2,6 +2,7 @@ use color_eyre::Result;
|
|||
use cookie_store::{Cookie, CookieDomain, CookieStore};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||
use echoed::{UserData, DEFAULT_ECHO360};
|
||||
use helper::echo;
|
||||
use ratatui::{
|
||||
prelude::*,
|
||||
widgets::{BorderType, Borders},
|
||||
|
@ -9,11 +10,12 @@ use ratatui::{
|
|||
use reqwest::Client;
|
||||
use reqwest_cookie_store::CookieStoreMutex;
|
||||
use std::{
|
||||
env::var,
|
||||
io::{Stdout, Write},
|
||||
sync::{mpsc as std_mpsc, Arc},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tui_confirm_dialog::{ConfirmDialog, ConfirmDialogState, Listener};
|
||||
use url::Url;
|
||||
use views::ViewContainer;
|
||||
|
@ -34,7 +36,7 @@ pub enum Command {
|
|||
|
||||
pub struct State {
|
||||
exit: ConfirmDialogState,
|
||||
exit_rx: std_mpsc::Receiver<Listener>,
|
||||
exit_rx: mpsc::Receiver<Listener>,
|
||||
client: Client,
|
||||
cookies: Arc<CookieStoreMutex>,
|
||||
echo_user: Option<UserData>,
|
||||
|
@ -42,7 +44,7 @@ pub struct State {
|
|||
|
||||
impl State {
|
||||
fn new(jar: CookieStoreMutex) -> Result<Self> {
|
||||
let (exit_tx, exit_rx) = std_mpsc::channel::<Listener>();
|
||||
let (exit_tx, exit_rx) = mpsc::channel::<Listener>(1);
|
||||
let jar = Arc::new(jar);
|
||||
|
||||
Ok(Self {
|
||||
|
@ -83,16 +85,27 @@ async fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|||
|
||||
// check if the cookies provide authentication
|
||||
let mut state = State::new(jar)?;
|
||||
let authenticated = helper::echo::check_auth(&mut state).await?;
|
||||
let mut authenticated = echo::check_auth(&mut state).await?;
|
||||
|
||||
// authenticate if env variables are provided
|
||||
if !authenticated {
|
||||
if let Ok((email, password)) = var("ECHO360_EMAIL")
|
||||
.and_then(|email| var("ECHO360_PASSWORD").map(|password| (email, password)))
|
||||
{
|
||||
authenticated = echo::authenticate(&email, &password, &mut state)
|
||||
.await
|
||||
.is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
let mut view: ViewContainer = if authenticated {
|
||||
Arc::new(Mutex::new(views::Home::default())) as ViewContainer
|
||||
Arc::new(Mutex::new(views::Courses::default())) as ViewContainer
|
||||
} else {
|
||||
Arc::new(Mutex::new(views::Auth::default())) as ViewContainer
|
||||
};
|
||||
|
||||
view.lock().await.setup();
|
||||
let (view_tx, view_rx) = std_mpsc::channel::<Command>();
|
||||
view.lock().await.setup(&state).await?;
|
||||
let (view_tx, mut view_rx) = mpsc::channel::<Command>(1);
|
||||
let mut exit_triggered = Instant::now();
|
||||
exit_triggered -= EXIT_TIMEOUT;
|
||||
|
||||
|
@ -100,6 +113,7 @@ async fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|||
loop {
|
||||
// check if the app should exit
|
||||
if let Ok((_, exit)) = state.exit_rx.try_recv() {
|
||||
println!("{:?}", exit);
|
||||
if exit.unwrap_or_default() {
|
||||
// save cookies
|
||||
// todo: decide on a place to store this
|
||||
|
@ -116,7 +130,7 @@ async fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|||
match view_rx.try_recv() {
|
||||
Ok(Command::ChangeView(new_view)) => {
|
||||
view = new_view;
|
||||
view.lock().await.setup();
|
||||
view.lock().await.setup(&state).await?;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
@ -149,7 +163,7 @@ async fn run(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|||
if key.code == KeyCode::Esc && exit_triggered.elapsed() < EXIT_TIMEOUT {
|
||||
continue;
|
||||
} else {
|
||||
state.exit.handle(key);
|
||||
state.exit.handle(key).await;
|
||||
exit_triggered = Instant::now();
|
||||
}
|
||||
|
|
@ -6,4 +6,4 @@ pub use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
|||
pub use ratatui::prelude::*;
|
||||
pub use std::any::Any;
|
||||
|
||||
pub type ViewSender = std::sync::mpsc::Sender<Command>;
|
||||
pub type ViewSender = tokio::sync::mpsc::Sender<Command>;
|
|
@ -13,17 +13,19 @@ macro_rules! import_view {
|
|||
};
|
||||
}
|
||||
|
||||
import_view!(Auth, Home);
|
||||
import_view!(Auth, Courses);
|
||||
|
||||
#[async_trait]
|
||||
pub trait View {
|
||||
fn setup(&mut self) {}
|
||||
async fn setup(&mut self, _state: &State) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn draw(&self, frame: &mut Frame, state: &State);
|
||||
async fn keypress(
|
||||
&mut self,
|
||||
key: KeyEvent,
|
||||
state: &mut State,
|
||||
command_tx: &ViewSender,
|
||||
_key: KeyEvent,
|
||||
_state: &mut State,
|
||||
_command_tx: &ViewSender,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -35,7 +37,8 @@ pub type ViewContainer = Arc<Mutex<dyn View + Send + Sync>>;
|
|||
macro_rules! change_view {
|
||||
($tx:expr, $view:ident) => {
|
||||
$tx.send(Command::ChangeView(std::sync::Arc::new(
|
||||
tokio::sync::Mutex::new(super::$view),
|
||||
)))?
|
||||
tokio::sync::Mutex::new(super::$view::default()),
|
||||
)))
|
||||
.await?
|
||||
};
|
||||
}
|
|
@ -18,9 +18,10 @@ pub struct Auth<'t> {
|
|||
|
||||
#[async_trait]
|
||||
impl<'t> View for Auth<'t> {
|
||||
fn setup(&mut self) {
|
||||
async fn setup(&mut self, _: &State) -> Result<()> {
|
||||
self.password.set_mask_char('\u{2022}');
|
||||
self.password.set_cursor_style(HIDE_STYLE);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut Frame, _: &State) {
|
||||
|
@ -120,7 +121,7 @@ impl<'t> View for Auth<'t> {
|
|||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
change_view!(tx, Home);
|
||||
change_view!(tx, Courses);
|
||||
}
|
||||
Err(e) => {
|
||||
self.error = Some(e.to_string().into());
|
70
crates/echoed/src/views/courses.rs
Normal file
70
crates/echoed/src/views/courses.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use color_eyre::eyre::eyre;
|
||||
use echoed::DEFAULT_ECHO360;
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Course {
|
||||
name: Cow<'static, str>,
|
||||
code: Cow<'static, str>,
|
||||
section_id: Cow<'static, str>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Courses {
|
||||
courses: Vec<Course>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl View for Courses {
|
||||
async fn setup(&mut self, state: &State) -> Result<()> {
|
||||
let res = state
|
||||
.client
|
||||
.get(format!("{}user/enrollments", DEFAULT_ECHO360.clone()))
|
||||
.send()
|
||||
.await?
|
||||
.json::<ijson::IValue>()
|
||||
.await?;
|
||||
|
||||
let enrollments = res["data"]
|
||||
.as_array()
|
||||
.map(|data| data[0]["userSections"].as_array())
|
||||
.flatten()
|
||||
.ok_or(eyre!("No courses found"))?
|
||||
.iter()
|
||||
.map(|course| {
|
||||
course["sectionId"].as_string().and_then(|section_id| {
|
||||
course["sectionName"].as_string().and_then(|code| {
|
||||
course["courseName"].as_string().map(|name| Course {
|
||||
name: name.to_string().into(),
|
||||
code: code.to_string().into(),
|
||||
section_id: section_id.to_string().into(),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.ok_or(eyre!("No courses found"))?;
|
||||
|
||||
self.courses = enrollments;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, frame: &mut Frame, state: &State) {
|
||||
// let user = state.echo_user.as_ref().unwrap();
|
||||
if let Some(x) = self.courses.first() {
|
||||
frame.render_widget(Line::raw(x.name.to_string()), frame.area());
|
||||
}
|
||||
}
|
||||
|
||||
async fn keypress(
|
||||
&mut self,
|
||||
key: KeyEvent,
|
||||
state: &mut State,
|
||||
_command_tx: &ViewSender,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
crates/reqwest_cookie_store_tokio
Submodule
1
crates/reqwest_cookie_store_tokio
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit aad0c697e209c6e79b15ff49b3bf0d05663492c3
|
1
crates/tui-confirm-dialog
Submodule
1
crates/tui-confirm-dialog
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 5c1156b1cd6ed765d4d06be82f8c027043aa0258
|
99
old.rs
99
old.rs
|
@ -1,99 +0,0 @@
|
|||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use regex::Regex;
|
||||
use reqwest::multipart::Form;
|
||||
use reqwest::{Client, Url};
|
||||
use reqwest_cookie_store::CookieStoreMutex;
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
const DEFAULT_BASE_URL: &str = "https://echo360.org.uk";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
#[cfg(feature = "dotenvy")]
|
||||
dotenvy::dotenv()?;
|
||||
|
||||
let jar = Arc::new(CookieStoreMutex::default());
|
||||
let client = Client::builder().cookie_provider(jar.clone()).build()?;
|
||||
let base_url = Url::parse(DEFAULT_BASE_URL)?;
|
||||
let domain = base_url.domain().unwrap();
|
||||
|
||||
// get the csrf token
|
||||
client.get(base_url.clone()).send().await?;
|
||||
|
||||
let csrf_token = jar
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(domain, "/", "PLAY_SESSION")
|
||||
.map(|cookie| {
|
||||
let cookie_value = cookie.value().to_string();
|
||||
cookie_value
|
||||
.split("&")
|
||||
.nth(1)
|
||||
.map(|part| part.split("=").last())
|
||||
.flatten()
|
||||
.map(|x| x.to_string())
|
||||
})
|
||||
.flatten()
|
||||
.ok_or(eyre!("No csrf token found"))?;
|
||||
|
||||
// authenticate
|
||||
client
|
||||
.post(format!("{base_url}login"))
|
||||
.query(&[("csrfToken", csrf_token)])
|
||||
.multipart(
|
||||
Form::new()
|
||||
.text("email", env::var("ECHO360_EMAIL")?)
|
||||
.text("password", env::var("ECHO360_PASSWORD")?),
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
// get institution id
|
||||
let insitution_id = jar
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(domain, "/", "PLAY_SESSION")
|
||||
.map(|cookie| {
|
||||
let cookie_value = cookie.value().to_string();
|
||||
cookie_value
|
||||
.split("&")
|
||||
.nth(1)
|
||||
.map(|part| part.split("=").last())
|
||||
.flatten()
|
||||
.map(|x| x.to_string())
|
||||
})
|
||||
.flatten()
|
||||
.ok_or(eyre!("No institution id found"))?;
|
||||
|
||||
println!("Institution ID: {}", insitution_id);
|
||||
|
||||
// get authenticated user's name
|
||||
let name = {
|
||||
let html = client
|
||||
.get(format!("{base_url}courses"))
|
||||
.send()
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
let first_name = Regex::new(r#"\\"firstName\\":\\"(\w+)\\""#)?
|
||||
.captures(&html)
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str();
|
||||
let last_name = Regex::new(r#"\\"lastName\\":\\"(\w+)\\""#)?
|
||||
.captures(&html)
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str();
|
||||
format!("{} {}", first_name, last_name)
|
||||
};
|
||||
|
||||
println!("Name: {}", name);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Home;
|
||||
|
||||
#[async_trait]
|
||||
impl View for Home {
|
||||
fn draw(&self, frame: &mut Frame, state: &State) {
|
||||
let user = state.echo_user.as_ref().unwrap();
|
||||
|
||||
frame.render_widget(Line::raw(user.name.to_string()), frame.area());
|
||||
}
|
||||
|
||||
async fn keypress(
|
||||
&mut self,
|
||||
key: KeyEvent,
|
||||
state: &mut State,
|
||||
_command_tx: &ViewSender,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
use super::{View, ViewContainer};
|
||||
use crate::prelude::*;
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Quit {
|
||||
previous: ViewContainer,
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
impl Quit {
|
||||
pub fn new(previous: ViewContainer) -> Self {
|
||||
Self {
|
||||
previous,
|
||||
yes: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl View for Quit {
|
||||
fn draw(&self, frame: &mut Frame) {
|
||||
let area = helper::border(frame, None);
|
||||
|
||||
// split vertically
|
||||
let [title_area, buttons_area] =
|
||||
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(area);
|
||||
|
||||
// render title
|
||||
frame.render_widget(
|
||||
Text::raw("Are you sure you would like to quit?").centered(),
|
||||
title_area,
|
||||
);
|
||||
|
||||
// render buttons
|
||||
let [yes_btn_area, no_btn_area] =
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.areas(buttons_area);
|
||||
|
||||
let yes_btn = Text::raw("Yes");
|
||||
let no_btn = Text::raw("No");
|
||||
|
||||
frame.render_widget(yes_btn, yes_btn_area);
|
||||
frame.render_widget(no_btn, no_btn_area);
|
||||
}
|
||||
|
||||
async fn keypress(&mut self, key: KeyEvent, tx: &ViewSender) -> Result<()> {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
tx.send(ViewCommand::Change(self.previous.clone())).await?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue