diff --git a/Cargo.lock b/Cargo.lock index c52cd1f..a21e125 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,17 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-trait" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-tungstenite" version = "0.18.0" @@ -609,6 +620,7 @@ name = "kkdisp" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "serde", "termion", ] @@ -618,6 +630,7 @@ name = "kkx" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "futures", "kkdisp", "misskey", diff --git a/kkdisp/Cargo.toml b/kkdisp/Cargo.toml index 532a6c5..df39906 100644 --- a/kkdisp/Cargo.toml +++ b/kkdisp/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0.68" +async-trait = "0.1.63" # termion = "2.0.1" [dependencies.serde] @@ -14,4 +15,4 @@ version = "1" features = ["std", "derive"] [dependencies.termion] -git = "https://sectorinf.com/emilis/termion" \ No newline at end of file +git = "https://sectorinf.com/emilis/termion" diff --git a/kkdisp/src/lib.rs b/kkdisp/src/lib.rs index a6b6fca..6e20cf4 100644 --- a/kkdisp/src/lib.rs +++ b/kkdisp/src/lib.rs @@ -227,7 +227,7 @@ where ); let mut events_iter = termion::async_stdin().events(); let mut plans = - PlanState::from_plans(view.init()?, base_colorset)?; + PlanState::from_plans(view.init().await?, base_colorset)?; plans.render(&mut screen)?; // FIXME: current loop means that pasting a string with len >1 // results in only the first character being rendered, until @@ -235,7 +235,7 @@ where loop { if let Some(msg) = view.query() { plans = plans.act_on( - view.update(Event::Message(msg))?, + view.update(Event::Message(msg)).await?, &mut screen, )?; continue; @@ -304,7 +304,8 @@ where // } if let Some(event) = event { - plans = plans.act_on(view.update(event)?, &mut screen)?; + plans = plans + .act_on(view.update(event).await?, &mut screen)?; } } Ok(()) @@ -368,10 +369,11 @@ pub enum Action { Nothing, } +#[async_trait::async_trait] pub trait View { type Message; - fn init( + async fn init( &mut self, ) -> std::result::Result; @@ -380,7 +382,7 @@ pub trait View { // from some sort of queue, or None if there's an empty queue. fn query(&mut self) -> Option; - fn update( + async fn update( &mut self, event: Event, ) -> std::result::Result; diff --git a/kkx/Cargo.toml b/kkx/Cargo.toml index ae35965..97328b2 100644 --- a/kkx/Cargo.toml +++ b/kkx/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" anyhow = "1.0.68" kkdisp = { version = "0.1.0", path = "../kkdisp" } futures = "0.3.25" +async-trait = "0.1.63" [dependencies.tokio] version = "1.24.2" diff --git a/kkx/src/login.rs b/kkx/src/login.rs index 1b73650..a881973 100644 --- a/kkx/src/login.rs +++ b/kkx/src/login.rs @@ -6,10 +6,12 @@ use kkdisp::{ view::{Event, Key}, Action, }; -use misskey::{StreamingClientExt, WebSocketClient}; -use tokio::sync::mpsc::{self, Receiver, Sender}; +use misskey::{ + websocket::WebSocketClientBuilder, ClientExt, StreamingClientExt, + WebSocketClient, +}; -use crate::Message; +use crate::{AuthDetail, Message}; pub enum LoginFocus { Hostname, Token, @@ -30,7 +32,7 @@ pub struct LoginPrompt { token: String, error: Option, focus: LoginFocus, - client: Option, + client: Option, } impl LoginPrompt { @@ -62,12 +64,13 @@ impl LoginPrompt { LoginFocus::OK => {} } } - pub fn query(&mut self) -> Option<()> { - todo!() + pub fn query(&mut self) -> Option { + self.client.take() } - pub fn attempt(&mut self) { - let hostname = self.hostname.clone(); + pub async fn attempt(&mut self) { + let hostname = + self.hostname.trim_end_matches('/').to_string(); if hostname.len() == 0 { self.error = Some("empty hostname".into()); self.focus = LoginFocus::Hostname; @@ -79,17 +82,42 @@ impl LoginPrompt { self.focus = LoginFocus::Token; return; } - todo!(); + let auth = AuthDetail { hostname, token }; + + match auth.normal().await { + Ok(client) => { + match client.recommended_users().try_next().await { + Ok(next) => { + eprintln!("next: {:#?}", next); + self.client = Some(auth); + } + Err(err) => { + self.error = Some(err.to_string()); + } + }; + } + Err(err) => { + self.error = Some(err.to_string()); + } + }; } - pub fn update( + fn down(&mut self) { + self.focus = match self.focus { + LoginFocus::Hostname => LoginFocus::Token, + LoginFocus::Token => LoginFocus::OK, + LoginFocus::OK => LoginFocus::Hostname, + }; + } + + pub async fn update( &mut self, event: Event, ) -> Result { match event { Event::Link(lnk) => { if lnk == "ok" { - self.attempt(); + self.attempt().await; Ok(Action::ReplaceAll(vec![self.plan()])) } else if lnk == "token" { self.focus = LoginFocus::Token; @@ -110,9 +138,10 @@ impl LoginPrompt { LoginFocus::Token => { self.focus = LoginFocus::OK; } - LoginFocus::OK => self.attempt(), + LoginFocus::OK => self.attempt().await, }, Key::Backspace => self.backspace(), + Key::Tab => self.down(), Key::Up => { self.focus = match self.focus { LoginFocus::Hostname => LoginFocus::OK, @@ -120,13 +149,7 @@ impl LoginPrompt { LoginFocus::OK => LoginFocus::Token, }; } - Key::Down => { - self.focus = match self.focus { - LoginFocus::Hostname => LoginFocus::Token, - LoginFocus::Token => LoginFocus::OK, - LoginFocus::OK => LoginFocus::Hostname, - }; - } + Key::Down => self.down(), Key::Char(c) => match self.focus { LoginFocus::Hostname => self.hostname.push(c), LoginFocus::Token => self.token.push(c), diff --git a/kkx/src/main.rs b/kkx/src/main.rs index f7065e1..c2f445b 100644 --- a/kkx/src/main.rs +++ b/kkx/src/main.rs @@ -8,6 +8,9 @@ use kkdisp::{ Action, PlanLayers, View, }; use login::LoginPrompt; +use misskey::{ + websocket::WebSocketClientBuilder, HttpClient, WebSocketClient, +}; use tokio::sync::mpsc::{self, Receiver, Sender}; mod login; @@ -17,6 +20,30 @@ async fn main() { std::process::exit(0); } +#[derive(Clone, Debug)] +pub struct AuthDetail { + pub hostname: String, + pub token: String, +} + +impl AuthDetail { + pub async fn streaming( + &self, + ) -> Result { + Ok(WebSocketClientBuilder::with_host(&self.hostname) + .token(&self.token) + .connect() + .await?) + } + pub async fn normal(&self) -> Result { + Ok(HttpClient::builder( + format!("https://{}", &self.hostname).as_str(), + ) + .token(&self.token) + .build()?) + } +} + pub enum Page { Login(LoginPrompt), } @@ -31,6 +58,7 @@ impl Page { pub struct AppView { recv: Receiver, + auth: Option, page: Page, } @@ -46,35 +74,42 @@ impl Default for AppView { Self { recv, page: Page::Login(LoginPrompt::new()), + auth: None, } } } pub enum Message {} +#[async_trait::async_trait] impl View for AppView { type Message = Message; - fn init( + async fn init( &mut self, ) -> std::result::Result { self.page.init() } - fn update( + async fn update( &mut self, event: Event, ) -> std::result::Result { let page = &mut self.page; match page { - Page::Login(login) => login.update(event), + Page::Login(login) => login.update(event).await, } } fn query(&mut self) -> Option { - match self.recv.try_recv() { - Ok(msg) => Some(msg), - Err(_) => None, + match &mut self.page { + Page::Login(log) => match log.query() { + Some(auth) => { + self.auth = Some(auth); + None + } + None => None, + }, } } }