From 950f31903637fb59774d96b86c1acd32cc525fae Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 3 Jan 2024 07:18:14 +0000 Subject: [PATCH] Actually post something! --- src/client.rs | 35 ++++++++++++++++++++++++++++++++++- src/main.rs | 6 +++++- src/posting.rs | 11 +++++++++-- src/tui.rs | 7 +++++-- src/types.rs | 4 ++-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/client.rs b/src/client.rs index 0dd1e16..44c9070 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,6 +5,7 @@ use std::io::Read; use super::auth::{AuthConfig,AuthError}; use super::config::ConfigLocation; use super::types::*; +use super::posting::Post; #[derive(Hash, Debug, PartialEq, Eq, Clone, Copy)] pub enum Boosts { Show, Hide } @@ -114,7 +115,6 @@ impl Req { } } - #[allow(unused)] // FIXME: remove this once we start using it fn post(url_suffix: &str) -> Self { Req { method: reqwest::Method::POST, @@ -152,6 +152,16 @@ impl ReqParam for bool { }.to_owned() } } +impl ReqParam for Visibility { + fn param_value(self) -> String { + // A bit roundabout, but means we get to reuse the 'rename' + // strings defined in types.rs + let encoded = serde_json::to_string(&self).expect("can't fail"); + let decoded: String = serde_json::from_str(&encoded) + .expect("can't fail either"); + decoded + } +} #[derive(Debug, Clone, PartialEq, Eq)] pub enum FeedExtend { @@ -782,4 +792,27 @@ impl Client { self.cache_account(&ac); Ok(ac) } + + pub fn post_status(&mut self, post: &Post) -> Result<(), ClientError> { + // FIXME: separate Post from a single status, so we can post threads + let req = Req::post("v1/statuses") + .param("status", post.text.trim_end_matches('\n')) + .param("visibility", post.m.visibility) + .param("language", &post.m.language); + let req = match &post.m.content_warning { + None => req, + Some(text) => req + .param("sensitive", true) + .param("spoiler_text", text), + }; + + let (url, req) = self.api_request(req)?; + let rsp = req.send()?; + let rspstatus = rsp.status(); + if !rspstatus.is_success() { + Err(ClientError::UrlError(url.clone(), rspstatus.to_string())) + } else { + Ok(()) + } + } } diff --git a/src/main.rs b/src/main.rs index f4d891c..65b07db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,10 @@ struct Args { /// Directory containing configuration files. #[arg(short, long)] config: Option, + + // Read-only mode: the client prevents accidental posting. + #[arg(short, long, action(clap::ArgAction::SetTrue))] + readonly: bool, } #[derive(Debug)] @@ -52,7 +56,7 @@ fn main_inner() -> Result<(), TopLevelError> { None => ConfigLocation::default()?, Some(dir) => ConfigLocation::from_pathbuf(dir), }; - Tui::run(&cfgloc)?; + Tui::run(&cfgloc, cli.readonly)?; Ok(()) } diff --git a/src/posting.rs b/src/posting.rs index c7e7dd6..030dc0a 100644 --- a/src/posting.rs +++ b/src/posting.rs @@ -236,6 +236,13 @@ impl PostMenu { self.post.m.language.clone(), EditLang); LogicalAction::Nothing } + + fn post(&self, client: &mut Client) -> LogicalAction { + match client.post_status(&self.post) { + Ok(_) => LogicalAction::Pop, + Err(_) => LogicalAction::Beep, // FIXME: report the error! + } + } } impl ActivityState for PostMenu { @@ -283,12 +290,12 @@ impl ActivityState for PostMenu { (lines, cursorpos) } - fn handle_keypress(&mut self, key: OurKey, _client: &mut Client) -> + fn handle_keypress(&mut self, key: OurKey, client: &mut Client) -> LogicalAction { match self.mode { Normal => match key { - Space => LogicalAction::Beep, // FIXME: post + Space => self.post(client), Pr('q') | Pr('Q') => LogicalAction::Pop, Pr('a') | Pr('A') => LogicalAction::PostReEdit( self.post.clone()), diff --git a/src/tui.rs b/src/tui.rs index 17d7f9e..f496ee8 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -188,7 +188,9 @@ impl std::fmt::Display for TuiError { } impl Tui { - pub fn run(cfgloc: &ConfigLocation) -> Result<(), TuiError> { + pub fn run(cfgloc: &ConfigLocation, readonly: bool) -> + Result<(), TuiError> + { let (sender, receiver) = std::sync::mpsc::sync_channel(1); let input_sender = sender.clone(); @@ -202,7 +204,8 @@ impl Tui { } }); - let client = Client::new(cfgloc)?; + let mut client = Client::new(cfgloc)?; + client.set_writable(!readonly); stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; diff --git a/src/types.rs b/src/types.rs index 8414209..b1b9c77 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, NaiveDate, Utc}; -use serde::{Deserialize}; +use serde::{Deserialize, Serialize}; use strum::EnumIter; use std::boxed::Box; use std::option::Option; @@ -120,7 +120,7 @@ pub struct Application { pub website: Option, } -#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Copy, EnumIter)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, EnumIter)] pub enum Visibility { #[serde(rename = "public")] Public, #[serde(rename = "unlisted")] Unlisted, -- 2.30.2