chiark / gitweb /
Actually post something!
authorSimon Tatham <anakin@pobox.com>
Wed, 3 Jan 2024 07:18:14 +0000 (07:18 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 3 Jan 2024 07:36:00 +0000 (07:36 +0000)
src/client.rs
src/main.rs
src/posting.rs
src/tui.rs
src/types.rs

index 0dd1e1687706fcccd3be3158b8caf434f8046817..44c9070be2ac67e8fef8a7b58a3c77ae1ee00583 100644 (file)
@@ -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(())
+        }
+    }
 }
index f4d891c6d1330c1a8d3b37321a05c92e5695489c..65b07dbac70990437b9d83ca0fee13a27016f338 100644 (file)
@@ -10,6 +10,10 @@ struct Args {
     /// Directory containing configuration files.
     #[arg(short, long)]
     config: Option<std::path::PathBuf>,
+
+    // 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(())
 }
 
index c7e7dd6e67c48e3614b12fd25a7fcf77618d6305..030dc0a7c81a080059ec3d18253f5789a4cd0c2e 100644 (file)
@@ -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()),
index 17d7f9e2c700af2084a774b925b85782bc331bb6..f496ee83043c59a07a9ee4414b5928affe4fcbe7 100644 (file)
@@ -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()?;
index 8414209f461530c2ed51de32adf4ca59b605ee5b..b1b9c77d37cbe0a10cd933fb85a37f10dd2d38a8 100644 (file)
@@ -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<String>,
 }
 
-#[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,