chiark / gitweb /
Retrieve extra details about your own account.
authorSimon Tatham <anakin@pobox.com>
Sat, 13 Jan 2024 10:28:49 +0000 (10:28 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 13 Jan 2024 13:15:58 +0000 (13:15 +0000)
Turns out that the API data model has two related types: 'Account',
which you can retrieve for anyone, and 'CredentialAccount' which
contains an extra sub-object full of settings you can only set (or
see) for your own account.

In another language I could make those distinct actual data types,
one a subclass of the other adding the extra field. Here, it's easier
to just fudge it a bit, pretending that all Account objects
_optionally_ have the 'source' subobject, and when called on to
retrieve our own record, always doing it via the API call that
provides the extra.

This also introduces a bit of a wrinkle about caching the result - we
_do_ also receive copies of our own Account record in other
contexts (e.g. as the author of a status we posted that reappears in a
feed), and so we have to ensure we don't use those to overwrite our
cached record to delete the source. This is ugly, but I prefer it to
the other option, which is to maintain a _separate_ cache of our own
account record with the extra data, and have to worry about which
version of the rest of it is up to date.

src/client.rs
src/types.rs

index a4888271c43250eaec97428e1267aa44c9b513aa..bd771f2b9b1e5187229dd4f5f59dbcccb24660eb 100644 (file)
@@ -468,7 +468,17 @@ impl Client {
     }
 
     fn cache_account(&mut self, ac: &Account) {
-        self.accounts.insert(ac.id.to_string(), ac.clone());
+        let mut ac = ac.clone();
+        // Don't overwrite a cached account with a 'source' to one
+        // without.
+        if !ac.source.is_some() {
+            if let Some(source) = self.accounts.get(&ac.id)
+                .and_then(|ac| ac.source.as_ref())
+            {
+                ac.source = Some(source.clone());
+            }
+        }
+        self.accounts.insert(ac.id.to_string(), ac);
     }
 
     fn cache_status(&mut self, st: &Status) {
@@ -495,12 +505,25 @@ impl Client {
     }
 
     pub fn account_by_id(&mut self, id: &str) -> Result<Account, ClientError> {
-        if let Some(st) = self.accounts.get(id) {
-            return Ok(st.clone());
+        // If we're fetching our own account, we do it via the
+        // verify_credentials request, which gets the extra
+        // information. This also means we must repeat the request if
+        // we've already received a copy of our own account details
+        // via some other API and it doesn't contain the extra
+        // information.
+
+        if let Some(ac) = self.accounts.get(id) {
+            if ac.id != self.auth.account_id || ac.source.is_some() {
+                return Ok(ac.clone());
+            }
         }
 
-        let (url, rsp) = self.api_request(Req::get(
-            &("v1/accounts/".to_owned() + id)))?;
+        let req = if id == self.auth.account_id {
+            Req::get(&format!("v1/accounts/verify_credentials"))
+        } else {
+            Req::get(&format!("v1/accounts/{id}"))
+        };
+        let (url, rsp) = self.api_request(req)?;
         let rspstatus = rsp.status();
         let ac: Account = if !rspstatus.is_success() {
             Err(ClientError::UrlError(url.clone(), rspstatus.to_string()))
index 290399235f7e2a0c527b3455cab91f23862e1161..05dcce786760dcee4dfd521354cb3992f0d5c704 100644 (file)
@@ -111,6 +111,28 @@ pub struct Account {
     pub statuses_count: u64,
     pub followers_count: u64,
     pub following_count: u64,
+
+    // In the wire protocol, 'CredentialAccount' is a subclass of
+    // 'Account' containing this extra field, only available from some
+    // requests, and only for your own account. We regard it as an
+    // optional field of Account in general.
+    pub source: Option<AccountSourceDetails>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct AccountSourceDetails {
+    pub privacy: Visibility,
+    pub sensitive: bool,
+    pub language: Option<String>,
+    pub follow_requests_count: usize,
+    pub hide_collections: Option<bool>,
+    pub discoverable: Option<bool>,
+    pub indexable: Option<bool>,
+    // 'fields' and 'note' here differ from the ones in the main
+    // Account in that the bio, and the 'value' of each field, are in
+    // the source text form rather than renderable HTML.
+    pub note: String,
+    pub fields: Vec<AccountField>,
 }
 
 #[derive(Deserialize, Debug, Clone)]