Compare commits

...

8 commits

Author SHA1 Message Date
5f998e9773
chore: update apb usage
it should become a bit faster which makes no sense but before refs were
made from strings created on demand and then cloned again so this should
at least make sense
2024-11-20 19:55:29 +01:00
0dedb4d243
feat(apb): all owned by default
references are cool but it was mostly cloned anyway so may aswell be
consistent. it would be awesome to make a Node which works by references
eventually, so it can be Copy, but probably quite annoying to make
2024-11-20 19:19:20 +01:00
f7e1a1bbc0
feat: while crawling replies update likes/shares 2024-11-20 18:06:05 +01:00
e2535e32ab
chore(web): FieldError -> MalformedError
since now it is also thrown for mismatching nodes
2024-11-20 18:05:28 +01:00
298f0a3774
chore: replace .get/.extract (and use shortcuts) 2024-11-20 18:05:00 +01:00
7a78b07467
chore(apb): renamed .get and .extract
are now .inner() and .into_inner()
2024-11-20 18:03:31 +01:00
fc43183ce1
feat(apb): add optional shortcuts 2024-11-20 18:02:54 +01:00
f82e624fd9
chore(apb): into_inner -> into_value 2024-11-19 14:19:25 +01:00
50 changed files with 462 additions and 367 deletions

View file

@ -23,7 +23,7 @@ sea-orm = { version = "1.0", optional = true, default-features = false }
reqwest = { version = "0.12", features = ["json"], optional = true } reqwest = { version = "0.12", features = ["json"], optional = true }
[features] [features]
default = ["activitypub-miscellaneous-terms", "send"] default = ["activitypub-miscellaneous-terms", "send", "shortcuts"]
# extensions # extensions
activitypub-miscellaneous-terms = [] # https://swicg.github.io/miscellany/ activitypub-miscellaneous-terms = [] # https://swicg.github.io/miscellany/
activitypub-counters = [] # https://ns.alemi.dev/as/counters/# activitypub-counters = [] # https://ns.alemi.dev/as/counters/#
@ -38,6 +38,7 @@ jsonld = []
send = [] send = []
orm = ["dep:sea-orm"] orm = ["dep:sea-orm"]
fetch = ["dep:reqwest"] fetch = ["dep:reqwest"]
shortcuts = []
# providers # providers
unstructured = ["dep:serde_json"] unstructured = ["dep:serde_json"]
#TODO eventually also make a structured base? #TODO eventually also make a structured base?

View file

@ -3,16 +3,3 @@
pub struct FieldErr(pub &'static str); pub struct FieldErr(pub &'static str);
pub type Field<T> = Result<T, FieldErr>; pub type Field<T> = Result<T, FieldErr>;
// TODO this trait is really ad-hoc and has awful naming...
pub trait OptionalString {
fn str(self) -> Option<String>;
}
impl OptionalString for Field<&str> {
fn str(self) -> Option<String> {
self.ok().map(|x| x.to_string())
}
}

View file

@ -1,32 +1,32 @@
// TODO technically this is not part of ActivityStreams // TODO technically this is not part of ActivityStreams
pub trait PublicKey : super::Base { pub trait PublicKey : super::Base {
fn owner(&self) -> crate::Field<&str> { Err(crate::FieldErr("owner")) } fn owner(&self) -> crate::Field<String> { Err(crate::FieldErr("owner")) }
fn public_key_pem(&self) -> &str; fn public_key_pem(&self) -> String;
} }
pub trait PublicKeyMut : super::BaseMut { pub trait PublicKeyMut : super::BaseMut {
fn set_owner(self, val: Option<&str>) -> Self; fn set_owner(self, val: Option<String>) -> Self;
fn set_public_key_pem(self, val: &str) -> Self; fn set_public_key_pem(self, val: String) -> Self;
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl PublicKey for serde_json::Value { impl PublicKey for serde_json::Value {
crate::getter! { owner -> &str } crate::getter! { owner -> String }
fn public_key_pem(&self) -> &str { fn public_key_pem(&self) -> String {
self.get("publicKeyPem").map(|x| x.as_str().unwrap_or_default()).unwrap_or_default() self.get("publicKeyPem").and_then(|x| x.as_str()).unwrap_or_default().to_string()
} }
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl PublicKeyMut for serde_json::Value { impl PublicKeyMut for serde_json::Value {
crate::setter! { owner -> &str } crate::setter! { owner -> String }
fn set_public_key_pem(mut self, val: &str) -> Self { fn set_public_key_pem(mut self, val: String) -> Self {
self.as_object_mut().unwrap().insert( self.as_object_mut().unwrap().insert(
"publicKeyPem".to_string(), "publicKeyPem".to_string(),
serde_json::Value::String(val.to_string()), serde_json::Value::String(val),
); );
self self
} }

View file

@ -104,6 +104,11 @@ pub use key::{PublicKey, PublicKeyMut};
pub mod field; pub mod field;
pub use field::{Field, FieldErr}; pub use field::{Field, FieldErr};
#[cfg(feature = "shortcuts")]
pub mod shortcuts;
#[cfg(feature = "shortcuts")]
pub use shortcuts::Shortcuts;
#[cfg(feature = "jsonld")] #[cfg(feature = "jsonld")]
mod jsonld; mod jsonld;

View file

@ -137,11 +137,12 @@ macro_rules! getter {
} }
}; };
($name:ident -> &str) => { ($name:ident -> String) => {
paste::paste! { paste::paste! {
fn [< $name:snake >](&self) -> $crate::Field<&str> { fn [< $name:snake >](&self) -> $crate::Field<String> {
self.get(stringify!($name)) self.get(stringify!($name))
.and_then(|x| x.as_str()) .and_then(|x| x.as_str())
.map(|x| x.to_string())
.ok_or($crate::FieldErr(stringify!($name))) .ok_or($crate::FieldErr(stringify!($name)))
} }
} }
@ -225,11 +226,11 @@ macro_rules! setter {
} }
}; };
($name:ident -> &str) => { ($name:ident -> String) => {
paste::item! { paste::item! {
fn [< set_$name:snake >](mut self, val: Option<&str>) -> Self { fn [< set_$name:snake >](mut self, val: Option<String>) -> Self {
$crate::macros::set_maybe_value( $crate::macros::set_maybe_value(
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_string())) &mut self, stringify!($name), val.map(|x| serde_json::Value::String(x))
); );
self self
} }
@ -311,7 +312,7 @@ pub fn set_maybe_node(obj: &mut serde_json::Value, key: &str, node: crate::Node<
if node.is_empty() { if node.is_empty() {
set_maybe_value(obj, key, None) set_maybe_value(obj, key, None)
} else { } else {
set_maybe_value(obj, key, Some(node.into_inner())) set_maybe_value(obj, key, Some(node.into_value()))
} }
} }

View file

@ -43,6 +43,7 @@ impl<T : super::Base> From<Option<T>> for Node<T> {
impl<T : super::Base> Node<T> { impl<T : super::Base> Node<T> {
/// return reference to embedded object (or first if many are present) /// return reference to embedded object (or first if many are present)
#[deprecated = "use .inner() instead"]
pub fn get(&self) -> Option<&T> { pub fn get(&self) -> Option<&T> {
match self { match self {
Node::Empty | Node::Link(_) => None, Node::Empty | Node::Link(_) => None,
@ -51,7 +52,8 @@ impl<T : super::Base> Node<T> {
} }
} }
/// return reference to embedded object (or first if many are present) /// return embedded object (or first if many are present)
#[deprecated = "use .into_inner() instead"]
pub fn extract(self) -> Option<T> { pub fn extract(self) -> Option<T> {
match self { match self {
Node::Empty | Node::Link(_) => None, Node::Empty | Node::Link(_) => None,
@ -60,6 +62,26 @@ impl<T : super::Base> Node<T> {
} }
} }
/// return reference to embedded object (or first if many are present)
pub fn inner(&self) -> crate::Field<&T> {
match self {
Node::Empty => Err(crate::FieldErr("node is empty")),
Node::Link(_) => Err(crate::FieldErr("node has not been dereferenced")),
Node::Object(x) => Ok(x),
Node::Array(v) => v.iter().next().ok_or(crate::FieldErr("node contains no items"))?.inner(),
}
}
/// return embedded object (or first if many are present)
pub fn into_inner(self) -> crate::Field<T> {
match self {
Node::Empty => Err(crate::FieldErr("node is empty")),
Node::Link(_) => Err(crate::FieldErr("node has not been dereferenced")),
Node::Object(x) => Ok(*x),
Node::Array(v) => v.into_iter().next().ok_or(crate::FieldErr("node contains no items"))?.into_inner(),
}
}
/// true only if Node is empty /// true only if Node is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
matches!(self, Node::Empty) matches!(self, Node::Empty)
@ -91,7 +113,7 @@ impl<T : super::Base> Node<T> {
} }
/// returns id of object: url for link, id for object, None if empty or array /// returns id of object: url for link, id for object, None if empty or array
pub fn id(&self) -> crate::Field<&str> { pub fn id(&self) -> crate::Field<String> {
match self { match self {
Node::Empty => Err(crate::FieldErr("id")), Node::Empty => Err(crate::FieldErr("id")),
Node::Link(uri) => uri.href(), Node::Link(uri) => uri.href(),
@ -121,7 +143,7 @@ impl<T : super::Base> Node<T> {
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl Node<serde_json::Value> { impl Node<serde_json::Value> {
pub fn into_inner(self) -> serde_json::Value { pub fn into_value(self) -> serde_json::Value {
match self { match self {
Self::Object(x) => *x, Self::Object(x) => *x,
Self::Link(l) => serde_json::Value::String(l.href().unwrap_or_default().to_string()), Self::Link(l) => serde_json::Value::String(l.href().unwrap_or_default().to_string()),
@ -129,7 +151,7 @@ impl Node<serde_json::Value> {
Self::Array(arr) => serde_json::Value::Array( Self::Array(arr) => serde_json::Value::Array(
arr arr
.into_iter() .into_iter()
.map(|x| x.into_inner()) .map(|x| x.into_value())
.collect() .collect()
), ),
} }

52
apb/src/shortcuts.rs Normal file
View file

@ -0,0 +1,52 @@
use crate::{Collection, Object};
pub trait Shortcuts: crate::Object {
fn likes_count(&self) -> crate::Field<i32> {
let x = self
.likes()
.inner()?
.total_items()?
.min(i32::MAX as u64)
as i32;
Ok(x)
}
fn shares_count(&self) -> crate::Field<i32> {
let x = self
.shares()
.inner()?
.total_items()?
.min(i32::MAX as u64)
as i32;
Ok(x)
}
fn replies_count(&self) -> crate::Field<i32> {
let x = self
.replies()
.inner()?
.total_items()?
.min(i32::MAX as u64)
as i32;
Ok(x)
}
fn image_url(&self) -> crate::Field<String> {
let image_node = self.image();
let image = image_node.inner()?;
let url = image.url();
let id = url.id()?;
Ok(id.to_string())
}
fn icon_url(&self) -> crate::Field<String> {
let icon_node = self.icon();
let icon = icon_node.inner()?;
let url = icon.url();
let id = url.id()?;
Ok(id.to_string())
}
}
impl<T: crate::Object> Shortcuts for T {}

View file

@ -9,20 +9,20 @@ crate::strenum! {
} }
pub trait Base : crate::macros::MaybeSend { pub trait Base : crate::macros::MaybeSend {
fn id(&self) -> crate::Field<&str> { Err(crate::FieldErr("id")) } fn id(&self) -> crate::Field<String> { Err(crate::FieldErr("id")) }
fn base_type(&self) -> crate::Field<BaseType> { Err(crate::FieldErr("type")) } fn base_type(&self) -> crate::Field<BaseType> { Err(crate::FieldErr("type")) }
} }
pub trait BaseMut : crate::macros::MaybeSend { pub trait BaseMut : crate::macros::MaybeSend {
fn set_id(self, val: Option<&str>) -> Self; fn set_id(self, val: Option<String>) -> Self;
fn set_base_type(self, val: Option<BaseType>) -> Self; fn set_base_type(self, val: Option<BaseType>) -> Self;
} }
impl Base for String { impl Base for String {
fn id(&self) -> crate::Field<&str> { fn id(&self) -> crate::Field<String> {
Ok(self) Ok(self.clone())
} }
fn base_type(&self) -> crate::Field<BaseType> { fn base_type(&self) -> crate::Field<BaseType> {
@ -43,12 +43,13 @@ impl Base for serde_json::Value {
} }
} }
fn id(&self) -> crate::Field<&str> { fn id(&self) -> crate::Field<String> {
if self.is_string() { if self.is_string() {
Ok(self.as_str().ok_or(crate::FieldErr("id"))?) Ok(self.as_str().ok_or(crate::FieldErr("id"))?.to_string())
} else { } else {
self.get("id") self.get("id")
.and_then(|x| x.as_str()) .and_then(|x| x.as_str())
.map(|x| x.to_string())
.ok_or(crate::FieldErr("id")) .ok_or(crate::FieldErr("id"))
} }
} }
@ -56,6 +57,6 @@ impl Base for serde_json::Value {
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl BaseMut for serde_json::Value { impl BaseMut for serde_json::Value {
crate::setter! { id -> &str } crate::setter! { id -> String }
crate::setter! { base_type -> type BaseType } crate::setter! { base_type -> type BaseType }
} }

View file

@ -19,79 +19,82 @@ crate::strenum! {
pub trait Link : crate::Base { pub trait Link : crate::Base {
fn link_type(&self) -> Field<LinkType> { Err(FieldErr("type")) } fn link_type(&self) -> Field<LinkType> { Err(FieldErr("type")) }
fn href(&self) -> Field<&str>; fn href(&self) -> Field<String>;
fn rel(&self) -> Field<&str> { Err(FieldErr("rel")) } fn rel(&self) -> Field<String> { Err(FieldErr("rel")) }
fn media_type(&self) -> Field<&str> { Err(FieldErr("mediaType")) } // also in obj fn media_type(&self) -> Field<String> { Err(FieldErr("mediaType")) } // also in obj
fn name(&self) -> Field<&str> { Err(FieldErr("name")) } // also in obj fn name(&self) -> Field<String> { Err(FieldErr("name")) } // also in obj
fn hreflang(&self) -> Field<&str> { Err(FieldErr("hreflang")) } fn hreflang(&self) -> Field<String> { Err(FieldErr("hreflang")) }
fn height(&self) -> Field<u64> { Err(FieldErr("height")) } fn height(&self) -> Field<u64> { Err(FieldErr("height")) }
fn width(&self) -> Field<u64> { Err(FieldErr("width")) } fn width(&self) -> Field<u64> { Err(FieldErr("width")) }
fn preview(&self) -> Field<&str> { Err(FieldErr("linkPreview")) } // also in obj fn preview(&self) -> Field<String> { Err(FieldErr("linkPreview")) } // also in obj
} }
pub trait LinkMut : crate::BaseMut { pub trait LinkMut : crate::BaseMut {
fn set_link_type(self, val: Option<LinkType>) -> Self; fn set_link_type(self, val: Option<LinkType>) -> Self;
fn set_href(self, href: Option<&str>) -> Self; fn set_href(self, href: Option<String>) -> Self;
fn set_rel(self, val: Option<&str>) -> Self; fn set_rel(self, val: Option<String>) -> Self;
fn set_media_type(self, val: Option<&str>) -> Self; // also in obj fn set_media_type(self, val: Option<String>) -> Self; // also in obj
fn set_name(self, val: Option<&str>) -> Self; // also in obj fn set_name(self, val: Option<String>) -> Self; // also in obj
fn set_hreflang(self, val: Option<&str>) -> Self; fn set_hreflang(self, val: Option<String>) -> Self;
fn set_height(self, val: Option<u64>) -> Self; fn set_height(self, val: Option<u64>) -> Self;
fn set_width(self, val: Option<u64>) -> Self; fn set_width(self, val: Option<u64>) -> Self;
fn set_preview(self, val: Option<&str>) -> Self; // also in obj fn set_preview(self, val: Option<String>) -> Self; // also in obj
} }
impl Link for String { impl Link for String {
fn href(&self) -> Field<&str> { fn href(&self) -> Field<String> {
Ok(self) Ok(self.to_string())
} }
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl Link for serde_json::Value { impl Link for serde_json::Value {
// TODO this can fail, but it should never do! // TODO this can fail, but it should never do!
fn href(&self) -> Field<&str> { fn href(&self) -> Field<String> {
if self.is_string() { if self.is_string() {
self.as_str().ok_or(FieldErr("href")) self.as_str().map(|x| x.to_string()).ok_or(FieldErr("href"))
} else { } else {
self.get("href").and_then(|x| x.as_str()).ok_or(FieldErr("href")) self.get("href")
.and_then(|x| x.as_str())
.map(|x| x.to_string())
.ok_or(FieldErr("href"))
} }
} }
crate::getter! { link_type -> type LinkType } crate::getter! { link_type -> type LinkType }
crate::getter! { rel -> &str } crate::getter! { rel -> String }
crate::getter! { mediaType -> &str } crate::getter! { mediaType -> String }
crate::getter! { name -> &str } crate::getter! { name -> String }
crate::getter! { hreflang -> &str } crate::getter! { hreflang -> String }
crate::getter! { height -> u64 } crate::getter! { height -> u64 }
crate::getter! { width -> u64 } crate::getter! { width -> u64 }
crate::getter! { preview -> &str } crate::getter! { preview -> String }
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl LinkMut for serde_json::Value { impl LinkMut for serde_json::Value {
fn set_href(mut self, href: Option<&str>) -> Self { fn set_href(mut self, href: Option<String>) -> Self {
match &mut self { match &mut self {
serde_json::Value::Object(map) => { serde_json::Value::Object(map) => {
match href { match href {
Some(href) => map.insert( Some(href) => map.insert(
"href".to_string(), "href".to_string(),
serde_json::Value::String(href.to_string()) serde_json::Value::String(href)
), ),
None => map.remove("href"), None => map.remove("href"),
}; };
}, },
x => *x = serde_json::Value::String(href.unwrap_or_default().to_string()), x => *x = serde_json::Value::String(href.unwrap_or_default()),
} }
self self
} }
crate::setter! { link_type -> type LinkType } crate::setter! { link_type -> type LinkType }
crate::setter! { rel -> &str } crate::setter! { rel -> String }
crate::setter! { mediaType -> &str } crate::setter! { mediaType -> String }
crate::setter! { name -> &str } crate::setter! { name -> String }
crate::setter! { hreflang -> &str } crate::setter! { hreflang -> String }
crate::setter! { height -> u64 } crate::setter! { height -> u64 }
crate::setter! { width -> u64 } crate::setter! { width -> u64 }
crate::setter! { preview -> &str } crate::setter! { preview -> String }
} }

View file

@ -16,7 +16,7 @@ pub trait Actor : Object {
fn actor_type(&self) -> Field<ActorType> { Err(FieldErr("type")) } fn actor_type(&self) -> Field<ActorType> { Err(FieldErr("type")) }
/// A short username which may be used to refer to the actor, with no uniqueness guarantees. /// A short username which may be used to refer to the actor, with no uniqueness guarantees.
fn preferred_username(&self) -> Field<&str> { Err(FieldErr("preferredUsername")) } fn preferred_username(&self) -> Field<String> { Err(FieldErr("preferredUsername")) }
/// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox. /// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor; see 5.2 Inbox.
fn inbox(&self) -> Node<Self::Collection>; fn inbox(&self) -> Node<Self::Collection>;
/// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox. /// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.
@ -64,17 +64,17 @@ pub trait Actor : Object {
pub trait Endpoints : Object { pub trait Endpoints : Object {
/// Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being the id of the requested ActivityStreams object. /// Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being the id of the requested ActivityStreams object.
fn proxy_url(&self) -> Field<&str> { Err(FieldErr("proxyUrl")) } fn proxy_url(&self) -> Field<String> { Err(FieldErr("proxyUrl")) }
/// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant. /// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
fn oauth_authorization_endpoint(&self) -> Field<&str> { Err(FieldErr("oauthAuthorizationEndpoint")) } fn oauth_authorization_endpoint(&self) -> Field<String> { Err(FieldErr("oauthAuthorizationEndpoint")) }
/// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a client may acquire an access token. /// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a client may acquire an access token.
fn oauth_token_endpoint(&self) -> Field<&str> { Err(FieldErr("oauthTokenEndpoint")) } fn oauth_token_endpoint(&self) -> Field<String> { Err(FieldErr("oauthTokenEndpoint")) }
/// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which browser-authenticated users may authorize a client's public key for client to server interactions. /// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which browser-authenticated users may authorize a client's public key for client to server interactions.
fn provide_client_key(&self) -> Field<&str> { Err(FieldErr("provideClientKey")) } fn provide_client_key(&self) -> Field<String> { Err(FieldErr("provideClientKey")) }
/// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to act on behalf of the actor in interacting with foreign servers. /// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to act on behalf of the actor in interacting with foreign servers.
fn sign_client_key(&self) -> Field<&str> { Err(FieldErr("signClientKey")) } fn sign_client_key(&self) -> Field<String> { Err(FieldErr("signClientKey")) }
/// An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers. sharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint. /// An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers. sharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint.
fn shared_inbox(&self) -> Field<&str> { Err(FieldErr("sharedInbox")) } fn shared_inbox(&self) -> Field<String> { Err(FieldErr("sharedInbox")) }
} }
pub trait ActorMut : ObjectMut { pub trait ActorMut : ObjectMut {
@ -82,7 +82,7 @@ pub trait ActorMut : ObjectMut {
type Endpoints : Endpoints; type Endpoints : Endpoints;
fn set_actor_type(self, val: Option<ActorType>) -> Self; fn set_actor_type(self, val: Option<ActorType>) -> Self;
fn set_preferred_username(self, val: Option<&str>) -> Self; fn set_preferred_username(self, val: Option<String>) -> Self;
fn set_inbox(self, val: Node<Self::Collection>) -> Self; fn set_inbox(self, val: Node<Self::Collection>) -> Self;
fn set_outbox(self, val: Node<Self::Collection>) -> Self; fn set_outbox(self, val: Node<Self::Collection>) -> Self;
fn set_following(self, val: Node<Self::Collection>) -> Self; fn set_following(self, val: Node<Self::Collection>) -> Self;
@ -122,17 +122,17 @@ pub trait ActorMut : ObjectMut {
pub trait EndpointsMut : ObjectMut { pub trait EndpointsMut : ObjectMut {
/// Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being the id of the requested ActivityStreams object. /// Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being the id of the requested ActivityStreams object.
fn set_proxy_url(self, val: Option<&str>) -> Self; fn set_proxy_url(self, val: Option<String>) -> Self;
/// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant. /// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
fn set_oauth_authorization_endpoint(self, val: Option<&str>) -> Self; fn set_oauth_authorization_endpoint(self, val: Option<String>) -> Self;
/// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a client may acquire an access token. /// If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions, this endpoint specifies a URI at which a client may acquire an access token.
fn set_oauth_token_endpoint(self, val: Option<&str>) -> Self; fn set_oauth_token_endpoint(self, val: Option<String>) -> Self;
/// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which browser-authenticated users may authorize a client's public key for client to server interactions. /// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which browser-authenticated users may authorize a client's public key for client to server interactions.
fn set_provide_client_key(self, val: Option<&str>) -> Self; fn set_provide_client_key(self, val: Option<String>) -> Self;
/// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to act on behalf of the actor in interacting with foreign servers. /// If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization, this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to act on behalf of the actor in interacting with foreign servers.
fn set_sign_client_key(self, val: Option<&str>) -> Self; fn set_sign_client_key(self, val: Option<String>) -> Self;
/// An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers. sharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint. /// An optional endpoint used for wide delivery of publicly addressed activities and activities sent to followers. sharedInbox endpoints SHOULD also be publicly readable OrderedCollection objects containing objects addressed to the Public special collection. Reading from the sharedInbox endpoint MUST NOT present objects which are not addressed to the Public endpoint.
fn set_shared_inbox(self, val: Option<&str>) -> Self; fn set_shared_inbox(self, val: Option<String>) -> Self;
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
@ -141,7 +141,7 @@ impl Actor for serde_json::Value {
type Endpoints = serde_json::Value; type Endpoints = serde_json::Value;
crate::getter! { actorType -> type ActorType } crate::getter! { actorType -> type ActorType }
crate::getter! { preferredUsername -> &str } crate::getter! { preferredUsername -> String }
crate::getter! { inbox -> node Self::Collection } crate::getter! { inbox -> node Self::Collection }
crate::getter! { outbox -> node Self::Collection } crate::getter! { outbox -> node Self::Collection }
crate::getter! { following -> node Self::Collection } crate::getter! { following -> node Self::Collection }
@ -181,12 +181,12 @@ impl Actor for serde_json::Value {
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl Endpoints for serde_json::Value { impl Endpoints for serde_json::Value {
crate::getter! { proxyUrl -> &str } crate::getter! { proxyUrl -> String }
crate::getter! { oauthAuthorizationEndpoint -> &str } crate::getter! { oauthAuthorizationEndpoint -> String }
crate::getter! { oauthTokenEndpoint -> &str } crate::getter! { oauthTokenEndpoint -> String }
crate::getter! { provideClientKey -> &str } crate::getter! { provideClientKey -> String }
crate::getter! { signClientKey -> &str } crate::getter! { signClientKey -> String }
crate::getter! { sharedInbox -> &str } crate::getter! { sharedInbox -> String }
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
@ -195,7 +195,7 @@ impl ActorMut for serde_json::Value {
type Endpoints = serde_json::Value; type Endpoints = serde_json::Value;
crate::setter! { actor_type -> type ActorType } crate::setter! { actor_type -> type ActorType }
crate::setter! { preferredUsername -> &str } crate::setter! { preferredUsername -> String }
crate::setter! { inbox -> node Self::Collection } crate::setter! { inbox -> node Self::Collection }
crate::setter! { outbox -> node Self::Collection } crate::setter! { outbox -> node Self::Collection }
crate::setter! { following -> node Self::Collection } crate::setter! { following -> node Self::Collection }
@ -236,10 +236,10 @@ impl ActorMut for serde_json::Value {
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
impl EndpointsMut for serde_json::Value { impl EndpointsMut for serde_json::Value {
crate::setter! { proxyUrl -> &str } crate::setter! { proxyUrl -> String }
crate::setter! { oauthAuthorizationEndpoint -> &str } crate::setter! { oauthAuthorizationEndpoint -> String }
crate::setter! { oauthTokenEndpoint -> &str } crate::setter! { oauthTokenEndpoint -> String }
crate::setter! { provideClientKey -> &str } crate::setter! { provideClientKey -> String }
crate::setter! { signClientKey -> &str } crate::setter! { signClientKey -> String }
crate::setter! { sharedInbox -> &str } crate::setter! { sharedInbox -> String }
} }

View file

@ -52,14 +52,14 @@ pub trait Object : Base {
/// The content or textual representation of the Object encoded as a JSON string. By default, the value of content is HTML /// The content or textual representation of the Object encoded as a JSON string. By default, the value of content is HTML
/// The mediaType property can be used in the object to indicate a different content type /// The mediaType property can be used in the object to indicate a different content type
/// The content MAY be expressed using multiple language-tagged values /// The content MAY be expressed using multiple language-tagged values
fn content(&self) -> Field<&str> { Err(FieldErr("content")) } // TODO handle language maps fn content(&self) -> Field<String> { Err(FieldErr("content")) } // TODO handle language maps
/// Identifies the context within which the object exists or an activity was performed /// Identifies the context within which the object exists or an activity was performed
/// The notion of "context" used is intentionally vague /// The notion of "context" used is intentionally vague
/// The intended function is to serve as a means of grouping objects and activities that share a common originating context or purpose /// The intended function is to serve as a means of grouping objects and activities that share a common originating context or purpose
/// An example could be all activities relating to a common project or event /// An example could be all activities relating to a common project or event
fn context(&self) -> Node<Self::Object> { Node::Empty } fn context(&self) -> Node<Self::Object> { Node::Empty }
/// A simple, human-readable, plain-text name for the object. HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values /// A simple, human-readable, plain-text name for the object. HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values
fn name(&self) -> Field<&str> { Err(FieldErr("name")) } // also in link // TODO handle language maps fn name(&self) -> Field<String> { Err(FieldErr("name")) } // also in link // TODO handle language maps
/// The date and time describing the actual or expected ending time of the object /// The date and time describing the actual or expected ending time of the object
/// When used with an Activity object, for instance, the endTime property specifies the moment the activity concluded or is expected to conclude. /// When used with an Activity object, for instance, the endTime property specifies the moment the activity concluded or is expected to conclude.
fn end_time(&self) -> Field<chrono::DateTime<chrono::Utc>> { Err(FieldErr("endTime")) } fn end_time(&self) -> Field<chrono::DateTime<chrono::Utc>> { Err(FieldErr("endTime")) }
@ -89,7 +89,7 @@ pub trait Object : Base {
/// When used with an Activity object, for instance, the startTime property specifies the moment the activity began or is scheduled to begin. /// When used with an Activity object, for instance, the startTime property specifies the moment the activity began or is scheduled to begin.
fn start_time(&self) -> Field<chrono::DateTime<chrono::Utc>> { Err(FieldErr("startTime")) } fn start_time(&self) -> Field<chrono::DateTime<chrono::Utc>> { Err(FieldErr("startTime")) }
/// A natural language summarization of the object encoded as HTML. Multiple language tagged summaries MAY be provided /// A natural language summarization of the object encoded as HTML. Multiple language tagged summaries MAY be provided
fn summary(&self) -> Field<&str> { Err(FieldErr("summary")) } fn summary(&self) -> Field<String> { Err(FieldErr("summary")) }
/// One or more "tags" that have been associated with an objects. A tag can be any kind of Object /// One or more "tags" that have been associated with an objects. A tag can be any kind of Object
/// The key difference between attachment and tag is that the former implies association by inclusion, while the latter implies associated by reference /// The key difference between attachment and tag is that the former implies association by inclusion, while the latter implies associated by reference
// TODO technically this is an object? but spec says that it works my reference, idk // TODO technically this is an object? but spec says that it works my reference, idk
@ -107,10 +107,10 @@ pub trait Object : Base {
/// When used on a Link, identifies the MIME media type of the referenced resource. /// When used on a Link, identifies the MIME media type of the referenced resource.
/// When used on an Object, identifies the MIME media type of the value of the content property. /// When used on an Object, identifies the MIME media type of the value of the content property.
/// If not specified, the content property is assumed to contain text/html content. /// If not specified, the content property is assumed to contain text/html content.
fn media_type(&self) -> Field<&str> { Err(FieldErr("mediaType")) } // also in link fn media_type(&self) -> Field<String> { Err(FieldErr("mediaType")) } // also in link
/// When the object describes a time-bound resource, such as an audio or video, a meeting, etc, the duration property indicates the object's approximate duration. /// When the object describes a time-bound resource, such as an audio or video, a meeting, etc, the duration property indicates the object's approximate duration.
/// The value MUST be expressed as an xsd:duration as defined by [ xmlschema11-2], section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S"). /// The value MUST be expressed as an xsd:duration as defined by [ xmlschema11-2], section 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S").
fn duration(&self) -> Field<&str> { Err(FieldErr("duration")) } // TODO how to parse xsd:duration ? fn duration(&self) -> Field<String> { Err(FieldErr("duration")) } // TODO how to parse xsd:duration ?
#[cfg(feature = "activitypub-miscellaneous-terms")] #[cfg(feature = "activitypub-miscellaneous-terms")]
fn sensitive(&self) -> Field<bool> { Err(FieldErr("sensitive")) } fn sensitive(&self) -> Field<bool> { Err(FieldErr("sensitive")) }
@ -129,7 +129,7 @@ pub trait Object : Base {
fn as_document(&self) -> Result<&Self::Document, FieldErr> { Err(FieldErr("type")) } fn as_document(&self) -> Result<&Self::Document, FieldErr> { Err(FieldErr("type")) }
#[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?! #[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?!
fn value(&self) -> Field<&str> { Err(FieldErr("value")) } fn value(&self) -> Field<String> { Err(FieldErr("value")) }
} }
pub trait ObjectMut : BaseMut { pub trait ObjectMut : BaseMut {
@ -143,9 +143,9 @@ pub trait ObjectMut : BaseMut {
fn set_attachment(self, val: Node<Self::Object>) -> Self; fn set_attachment(self, val: Node<Self::Object>) -> Self;
fn set_attributed_to(self, val: Node<Self::Actor>) -> Self; fn set_attributed_to(self, val: Node<Self::Actor>) -> Self;
fn set_audience(self, val: Node<Self::Actor>) -> Self; fn set_audience(self, val: Node<Self::Actor>) -> Self;
fn set_content(self, val: Option<&str>) -> Self; // TODO handle language maps fn set_content(self, val: Option<String>) -> Self; // TODO handle language maps
fn set_context(self, val: Node<Self::Object>) -> Self; fn set_context(self, val: Node<Self::Object>) -> Self;
fn set_name(self, val: Option<&str>) -> Self; // also in link // TODO handle language maps fn set_name(self, val: Option<String>) -> Self; // also in link // TODO handle language maps
fn set_end_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self; fn set_end_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_generator(self, val: Node<Self::Actor>) -> Self; fn set_generator(self, val: Node<Self::Actor>) -> Self;
fn set_icon(self, val: Node<Self::Document>) -> Self; fn set_icon(self, val: Node<Self::Document>) -> Self;
@ -159,15 +159,15 @@ pub trait ObjectMut : BaseMut {
fn set_likes(self, val: Node<Self::Collection>) -> Self; fn set_likes(self, val: Node<Self::Collection>) -> Self;
fn set_shares(self, val: Node<Self::Collection>) -> Self; fn set_shares(self, val: Node<Self::Collection>) -> Self;
fn set_start_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self; fn set_start_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_summary(self, val: Option<&str>) -> Self; fn set_summary(self, val: Option<String>) -> Self;
fn set_tag(self, val: Node<Self::Object>) -> Self; fn set_tag(self, val: Node<Self::Object>) -> Self;
fn set_url(self, val: Node<Self::Link>) -> Self; fn set_url(self, val: Node<Self::Link>) -> Self;
fn set_to(self, val: Node<Self::Link>) -> Self; fn set_to(self, val: Node<Self::Link>) -> Self;
fn set_bto(self, val: Node<Self::Link>) -> Self; fn set_bto(self, val: Node<Self::Link>) -> Self;
fn set_cc(self, val: Node<Self::Link>) -> Self; fn set_cc(self, val: Node<Self::Link>) -> Self;
fn set_bcc(self, val: Node<Self::Link>) -> Self; fn set_bcc(self, val: Node<Self::Link>) -> Self;
fn set_media_type(self, val: Option<&str>) -> Self; // also in link fn set_media_type(self, val: Option<String>) -> Self; // also in link
fn set_duration(self, val: Option<&str>) -> Self; // TODO how to parse xsd:duration ? fn set_duration(self, val: Option<String>) -> Self; // TODO how to parse xsd:duration ?
#[cfg(feature = "activitypub-miscellaneous-terms")] #[cfg(feature = "activitypub-miscellaneous-terms")]
fn set_sensitive(self, val: Option<bool>) -> Self; fn set_sensitive(self, val: Option<bool>) -> Self;
@ -181,7 +181,7 @@ pub trait ObjectMut : BaseMut {
fn set_conversation(self, val: Node<Self::Object>) -> Self; fn set_conversation(self, val: Node<Self::Object>) -> Self;
#[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?! #[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?!
fn set_value(self, val: Option<&str>) -> Self; fn set_value(self, val: Option<String>) -> Self;
} }
#[cfg(feature = "unstructured")] #[cfg(feature = "unstructured")]
@ -197,9 +197,9 @@ impl Object for serde_json::Value {
crate::getter! { attachment -> node <Self as Object>::Object } crate::getter! { attachment -> node <Self as Object>::Object }
crate::getter! { attributedTo -> node Self::Actor } crate::getter! { attributedTo -> node Self::Actor }
crate::getter! { audience -> node Self::Actor } crate::getter! { audience -> node Self::Actor }
crate::getter! { content -> &str } crate::getter! { content -> String }
crate::getter! { context -> node <Self as Object>::Object } crate::getter! { context -> node <Self as Object>::Object }
crate::getter! { name -> &str } crate::getter! { name -> String }
crate::getter! { endTime -> chrono::DateTime<chrono::Utc> } crate::getter! { endTime -> chrono::DateTime<chrono::Utc> }
crate::getter! { generator -> node Self::Actor } crate::getter! { generator -> node Self::Actor }
crate::getter! { icon -> node Self::Document } crate::getter! { icon -> node Self::Document }
@ -213,14 +213,14 @@ impl Object for serde_json::Value {
crate::getter! { likes -> node Self::Collection } crate::getter! { likes -> node Self::Collection }
crate::getter! { shares -> node Self::Collection } crate::getter! { shares -> node Self::Collection }
crate::getter! { startTime -> chrono::DateTime<chrono::Utc> } crate::getter! { startTime -> chrono::DateTime<chrono::Utc> }
crate::getter! { summary -> &str } crate::getter! { summary -> String }
crate::getter! { tag -> node <Self as Object>::Object } crate::getter! { tag -> node <Self as Object>::Object }
crate::getter! { to -> node Self::Link } crate::getter! { to -> node Self::Link }
crate::getter! { bto -> node Self::Link } crate::getter! { bto -> node Self::Link }
crate::getter! { cc -> node Self::Link } crate::getter! { cc -> node Self::Link }
crate::getter! { bcc -> node Self::Link } crate::getter! { bcc -> node Self::Link }
crate::getter! { mediaType -> &str } crate::getter! { mediaType -> String }
crate::getter! { duration -> &str } crate::getter! { duration -> String }
crate::getter! { url -> node Self::Link } crate::getter! { url -> node Self::Link }
#[cfg(feature = "activitypub-miscellaneous-terms")] #[cfg(feature = "activitypub-miscellaneous-terms")]
@ -235,7 +235,7 @@ impl Object for serde_json::Value {
crate::getter! { conversation -> node <Self as Object>::Object } crate::getter! { conversation -> node <Self as Object>::Object }
#[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?! #[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?!
crate::getter! { value -> &str } crate::getter! { value -> String }
fn as_activity(&self) -> Result<&Self::Activity, FieldErr> { fn as_activity(&self) -> Result<&Self::Activity, FieldErr> {
match self.object_type()? { match self.object_type()? {
@ -278,9 +278,9 @@ impl ObjectMut for serde_json::Value {
crate::setter! { attachment -> node <Self as Object>::Object } crate::setter! { attachment -> node <Self as Object>::Object }
crate::setter! { attributedTo -> node Self::Actor } crate::setter! { attributedTo -> node Self::Actor }
crate::setter! { audience -> node Self::Actor } crate::setter! { audience -> node Self::Actor }
crate::setter! { content -> &str } crate::setter! { content -> String }
crate::setter! { context -> node <Self as Object>::Object } crate::setter! { context -> node <Self as Object>::Object }
crate::setter! { name -> &str } crate::setter! { name -> String }
crate::setter! { endTime -> chrono::DateTime<chrono::Utc> } crate::setter! { endTime -> chrono::DateTime<chrono::Utc> }
crate::setter! { generator -> node Self::Actor } crate::setter! { generator -> node Self::Actor }
crate::setter! { icon -> node Self::Document } crate::setter! { icon -> node Self::Document }
@ -294,14 +294,14 @@ impl ObjectMut for serde_json::Value {
crate::setter! { likes -> node Self::Collection } crate::setter! { likes -> node Self::Collection }
crate::setter! { shares -> node Self::Collection } crate::setter! { shares -> node Self::Collection }
crate::setter! { startTime -> chrono::DateTime<chrono::Utc> } crate::setter! { startTime -> chrono::DateTime<chrono::Utc> }
crate::setter! { summary -> &str } crate::setter! { summary -> String }
crate::setter! { tag -> node <Self as Object>::Object } crate::setter! { tag -> node <Self as Object>::Object }
crate::setter! { to -> node Self::Link } crate::setter! { to -> node Self::Link }
crate::setter! { bto -> node Self::Link} crate::setter! { bto -> node Self::Link}
crate::setter! { cc -> node Self::Link } crate::setter! { cc -> node Self::Link }
crate::setter! { bcc -> node Self::Link } crate::setter! { bcc -> node Self::Link }
crate::setter! { mediaType -> &str } crate::setter! { mediaType -> String }
crate::setter! { duration -> &str } crate::setter! { duration -> String }
crate::setter! { url -> node Self::Link } crate::setter! { url -> node Self::Link }
#[cfg(feature = "activitypub-miscellaneous-terms")] #[cfg(feature = "activitypub-miscellaneous-terms")]
@ -316,5 +316,5 @@ impl ObjectMut for serde_json::Value {
crate::setter! { conversation -> node <Self as Object>::Object } crate::setter! { conversation -> node <Self as Object>::Object }
#[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?! #[cfg(feature = "did-core")] // TODO this isn't from did-core actually!?!?!?!?!
crate::setter! { value -> &str } crate::setter! { value -> String }
} }

View file

@ -25,7 +25,7 @@ pub async fn fetch(ctx: upub::Context, uri: String, save: bool, actor: Option<St
let mut node = apb::Node::link(uri.to_string()); let mut node = apb::Node::link(uri.to_string());
if let apb::Node::Link(ref uri) = node { if let apb::Node::Link(ref uri) = node {
if let Ok(href) = uri.href() { if let Ok(href) = uri.href() {
node = upub::Context::request(reqwest::Method::GET, href, None, &from, &pkey, ctx.domain()) node = upub::Context::request(reqwest::Method::GET, &href, None, &from, &pkey, ctx.domain())
.await? .await?
.json::<serde_json::Value>() .json::<serde_json::Value>()
.await? .await?
@ -34,7 +34,7 @@ pub async fn fetch(ctx: upub::Context, uri: String, save: bool, actor: Option<St
} }
let obj = node.extract().expect("node still empty after fetch?"); let obj = node.into_inner().expect("node still empty after fetch?");
println!("{}", serde_json::to_string_pretty(&obj).unwrap()); println!("{}", serde_json::to_string_pretty(&obj).unwrap());

View file

@ -63,7 +63,7 @@ pub async fn nuke(ctx: upub::Context, for_real: bool, delete_posts: bool) -> Res
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let undo_activity = apb::new() let undo_activity = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Undo)) .set_activity_type(Some(apb::ActivityType::Undo))
.set_actor(apb::Node::link(activity.actor.clone())) .set_actor(apb::Node::link(activity.actor.clone()))
.set_object(apb::Node::object(undone)) .set_object(apb::Node::object(undone))
@ -73,7 +73,7 @@ pub async fn nuke(ctx: upub::Context, for_real: bool, delete_posts: bool) -> Res
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(activity.actor), actor: Set(activity.actor),
target: Set(None), target: Set(None),
@ -101,7 +101,7 @@ pub async fn nuke(ctx: upub::Context, for_real: bool, delete_posts: bool) -> Res
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let actor = object.attributed_to.unwrap_or_else(|| ctx.domain().to_string()); let actor = object.attributed_to.unwrap_or_else(|| ctx.domain().to_string());
let undo_activity = apb::new() let undo_activity = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Delete)) .set_activity_type(Some(apb::ActivityType::Delete))
.set_actor(apb::Node::link(actor.clone())) .set_actor(apb::Node::link(actor.clone()))
.set_object(apb::Node::link(object.id.clone())) .set_object(apb::Node::link(object.id.clone()))
@ -114,7 +114,7 @@ pub async fn nuke(ctx: upub::Context, for_real: bool, delete_posts: bool) -> Res
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(actor), actor: Set(actor),
target: Set(None), target: Set(None),

View file

@ -67,7 +67,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
RelayCommand::Follow { actor } => { RelayCommand::Follow { actor } => {
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let payload = apb::new() let payload = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Follow)) .set_activity_type(Some(apb::ActivityType::Follow))
.set_actor(apb::Node::link(ctx.base().to_string())) .set_actor(apb::Node::link(ctx.base().to_string()))
.set_object(apb::Node::link(actor.clone())) .set_object(apb::Node::link(actor.clone()))
@ -76,7 +76,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.set_published(Some(chrono::Utc::now())); .set_published(Some(chrono::Utc::now()));
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(ctx.base().to_string()), actor: Set(ctx.base().to_string()),
target: Set(None), target: Set(None),
@ -103,7 +103,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", relation.activity)))?; .ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", relation.activity)))?;
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let payload = apb::new() let payload = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Accept(apb::AcceptType::Accept))) .set_activity_type(Some(apb::ActivityType::Accept(apb::AcceptType::Accept)))
.set_actor(apb::Node::link(ctx.base().to_string())) .set_actor(apb::Node::link(ctx.base().to_string()))
.set_object(apb::Node::link(activity.id)) .set_object(apb::Node::link(activity.id))
@ -112,7 +112,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.set_published(Some(chrono::Utc::now())); .set_published(Some(chrono::Utc::now()));
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(ctx.base().to_string()), actor: Set(ctx.base().to_string()),
target: Set(None), target: Set(None),
@ -140,7 +140,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", accept_activity_id)))?; .ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", accept_activity_id)))?;
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let payload = apb::new() let payload = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Undo)) .set_activity_type(Some(apb::ActivityType::Undo))
.set_actor(apb::Node::link(ctx.base().to_string())) .set_actor(apb::Node::link(ctx.base().to_string()))
.set_object(apb::Node::object(activity.ap())) .set_object(apb::Node::object(activity.ap()))
@ -149,7 +149,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.set_published(Some(chrono::Utc::now())); .set_published(Some(chrono::Utc::now()));
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(ctx.base().to_string()), actor: Set(ctx.base().to_string()),
target: Set(None), target: Set(None),
@ -176,7 +176,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", relation.activity)))?; .ok_or_else(|| DbErr::RecordNotFound(format!("activity#{}", relation.activity)))?;
let aid = ctx.aid(&upub::Context::new_id()); let aid = ctx.aid(&upub::Context::new_id());
let payload = apb::new() let payload = apb::new()
.set_id(Some(&aid)) .set_id(Some(aid.clone()))
.set_activity_type(Some(apb::ActivityType::Undo)) .set_activity_type(Some(apb::ActivityType::Undo))
.set_actor(apb::Node::link(ctx.base().to_string())) .set_actor(apb::Node::link(ctx.base().to_string()))
.set_object(apb::Node::object(activity.ap())) .set_object(apb::Node::object(activity.ap()))
@ -185,7 +185,7 @@ pub async fn relay(ctx: upub::Context, action: RelayCommand) -> Result<(), Reque
.set_published(Some(chrono::Utc::now())); .set_published(Some(chrono::Utc::now()));
let job = upub::model::job::ActiveModel { let job = upub::model::job::ActiveModel {
internal: NotSet, internal: NotSet,
activity: Set(aid.clone()), activity: Set(aid),
job_type: Set(upub::model::job::JobType::Outbound), job_type: Set(upub::model::job::JobType::Outbound),
actor: Set(ctx.base().to_string()), actor: Set(ctx.base().to_string()),
target: Set(None), target: Set(None),

View file

@ -90,7 +90,7 @@ impl Entity {
impl Model { impl Model {
pub fn ap(self) -> serde_json::Value { pub fn ap(self) -> serde_json::Value {
apb::new() apb::new()
.set_id(Some(&self.id)) .set_id(Some(self.id))
.set_activity_type(Some(self.activity_type)) .set_activity_type(Some(self.activity_type))
.set_actor(apb::Node::link(self.actor)) .set_actor(apb::Node::link(self.actor))
.set_object(apb::Node::maybe_link(self.object)) .set_object(apb::Node::maybe_link(self.object))

View file

@ -1,6 +1,6 @@
use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns}; use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns};
use apb::{field::OptionalString, ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut}; use apb::{ActorMut, ActorType, BaseMut, DocumentMut, EndpointsMut, ObjectMut, PublicKeyMut};
use crate::ext::{JsonVec, TypeName}; use crate::ext::{JsonVec, TypeName};
@ -25,8 +25,8 @@ impl TypeName for Field {
impl<T: apb::Object> From<T> for Field { impl<T: apb::Object> From<T> for Field {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Field { Field {
name: value.name().str().unwrap_or_default(), name: value.name().unwrap_or_default().to_string(),
value: mdhtml::safe_html(value.value().unwrap_or_default()), value: mdhtml::safe_html(&value.value().unwrap_or_default()),
field_type: "PropertyValue".to_string(), // TODO can we try parsing this instead?? field_type: "PropertyValue".to_string(), // TODO can we try parsing this instead??
verified_at: None, // TODO where does verified_at come from? extend apb maybe verified_at: None, // TODO where does verified_at come from? extend apb maybe
} }
@ -203,10 +203,10 @@ impl Entity {
impl Model { impl Model {
pub fn ap(self) -> serde_json::Value { pub fn ap(self) -> serde_json::Value {
apb::new() apb::new()
.set_id(Some(&self.id)) .set_id(Some(self.id.clone()))
.set_actor_type(Some(self.actor_type)) .set_actor_type(Some(self.actor_type))
.set_name(self.name.as_deref()) .set_name(self.name)
.set_summary(self.summary.as_deref()) .set_summary(self.summary)
.set_icon(apb::Node::maybe_object(self.icon.map(|i| .set_icon(apb::Node::maybe_object(self.icon.map(|i|
apb::new() apb::new()
.set_document_type(Some(apb::DocumentType::Image)) .set_document_type(Some(apb::DocumentType::Image))
@ -225,7 +225,7 @@ impl Model {
)) ))
.set_published(Some(self.published)) .set_published(Some(self.published))
.set_updated(if self.updated != self.published { Some(self.updated) } else { None }) .set_updated(if self.updated != self.published { Some(self.updated) } else { None })
.set_preferred_username(Some(&self.preferred_username)) .set_preferred_username(Some(self.preferred_username))
.set_statuses_count(Some(self.statuses_count as u64)) .set_statuses_count(Some(self.statuses_count as u64))
.set_followers_count(Some(self.followers_count as u64)) .set_followers_count(Some(self.followers_count as u64))
.set_following_count(Some(self.following_count as u64)) .set_following_count(Some(self.following_count as u64))
@ -235,13 +235,13 @@ impl Model {
.set_followers(apb::Node::maybe_link(self.followers)) .set_followers(apb::Node::maybe_link(self.followers))
.set_public_key(apb::Node::object( .set_public_key(apb::Node::object(
apb::new() apb::new()
.set_id(Some(&format!("{}#main-key", self.id))) .set_id(Some(format!("{}#main-key", self.id)))
.set_owner(Some(&self.id)) .set_owner(Some(self.id))
.set_public_key_pem(&self.public_key) .set_public_key_pem(self.public_key)
)) ))
.set_endpoints(apb::Node::object( .set_endpoints(apb::Node::object(
apb::new() apb::new()
.set_shared_inbox(self.shared_inbox.as_deref()) .set_shared_inbox(self.shared_inbox)
)) ))
.set_also_known_as(apb::Node::links(self.also_known_as.0)) .set_also_known_as(apb::Node::links(self.also_known_as.0))
.set_moved_to(apb::Node::maybe_link(self.moved_to)) .set_moved_to(apb::Node::maybe_link(self.moved_to))

View file

@ -39,7 +39,7 @@ impl Model {
apb::new() apb::new()
.set_url(apb::Node::link(self.url)) .set_url(apb::Node::link(self.url))
.set_document_type(Some(self.document_type)) .set_document_type(Some(self.document_type))
.set_media_type(Some(&self.media_type)) .set_media_type(Some(self.media_type))
.set_name(self.name.as_deref()) .set_name(self.name)
} }
} }

View file

@ -163,12 +163,12 @@ impl Entity {
impl Model { impl Model {
pub fn ap(self) -> serde_json::Value { pub fn ap(self) -> serde_json::Value {
apb::new() apb::new()
.set_id(Some(&self.id)) .set_id(Some(self.id))
.set_object_type(Some(self.object_type)) .set_object_type(Some(self.object_type))
.set_attributed_to(apb::Node::maybe_link(self.attributed_to)) .set_attributed_to(apb::Node::maybe_link(self.attributed_to))
.set_name(self.name.as_deref()) .set_name(self.name)
.set_summary(self.summary.as_deref()) .set_summary(self.summary)
.set_content(self.content.as_deref()) .set_content(self.content)
.set_image(apb::Node::maybe_object(self.image.map(|x| .set_image(apb::Node::maybe_object(self.image.map(|x|
apb::new() apb::new()
.set_document_type(Some(apb::DocumentType::Image)) .set_document_type(Some(apb::DocumentType::Image))

View file

@ -12,8 +12,8 @@ impl RichMention {
use apb::LinkMut; use apb::LinkMut;
apb::new() apb::new()
.set_link_type(Some(apb::LinkType::Mention)) .set_link_type(Some(apb::LinkType::Mention))
.set_href(Some(&self.id)) .set_href(Some(self.id))
.set_name(Some(&self.fqn)) .set_name(Some(self.fqn))
} }
} }
@ -25,7 +25,7 @@ impl RichHashtag {
pub fn ap(self) -> serde_json::Value { pub fn ap(self) -> serde_json::Value {
use apb::LinkMut; use apb::LinkMut;
apb::new() apb::new()
.set_name(Some(&format!("#{}", self.hash.name))) .set_name(Some(format!("#{}", self.hash.name)))
.set_link_type(Some(apb::LinkType::Hashtag)) .set_link_type(Some(apb::LinkType::Hashtag))
} }
} }

View file

@ -1,14 +1,14 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use apb::{Activity, Actor, ActorMut, Base, Collection, CollectionPage, Object}; use apb::{Shortcuts, Activity, Actor, ActorMut, Base, Collection, CollectionPage, Object};
use reqwest::{header::{ACCEPT, CONTENT_TYPE, USER_AGENT}, Method, Response}; use reqwest::{header::{ACCEPT, CONTENT_TYPE, USER_AGENT}, Method, Response};
use sea_orm::{ActiveValue::Set, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, IntoActiveModel, NotSet, QueryFilter}; use sea_orm::{ActiveValue::Set, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, IntoActiveModel, NotSet, QueryFilter, ActiveModelTrait};
use crate::traits::normalize::AP;
use super::{Addresser, Cloaker, Normalizer}; use super::{Addresser, Cloaker, Normalizer};
use httpsign::HttpSignature; use httpsign::HttpSignature;
use crate::AP;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Pull<T> { pub enum Pull<T> {
Actor(T), Actor(T),
@ -184,7 +184,7 @@ impl Fetcher for crate::Context {
if depth >= self.cfg().security.max_id_redirects { if depth >= self.cfg().security.max_id_redirects {
return Err(RequestError::TooManyRedirects); return Err(RequestError::TooManyRedirects);
} }
return self.pull(doc_id).await; return self.pull(&doc_id).await;
} }
match document.object_type()? { match document.object_type()? {
@ -299,7 +299,7 @@ impl Fetcher for crate::Context {
// TODO try fetching these numbers from audience/generator fields to avoid making 2 more GETs every time // TODO try fetching these numbers from audience/generator fields to avoid making 2 more GETs every time
if let Ok(followers_url) = document.followers().id() { if let Ok(followers_url) = document.followers().id() {
let req = Self::request( let req = Self::request(
Method::GET, followers_url, None, Method::GET, &followers_url, None,
self.base(), self.pkey(), self.domain(), self.base(), self.pkey(), self.domain(),
).await; ).await;
if let Ok(res) = req { if let Ok(res) = req {
@ -313,7 +313,7 @@ impl Fetcher for crate::Context {
if let Ok(following_url) = document.following().id() { if let Ok(following_url) = document.following().id() {
let req = Self::request( let req = Self::request(
Method::GET, following_url, None, Method::GET, &following_url, None,
self.base(), self.pkey(), self.domain(), self.base(), self.pkey(), self.domain(),
).await; ).await;
if let Ok(res) = req { if let Ok(res) = req {
@ -362,7 +362,7 @@ impl Fetcher for crate::Context {
let document = self.pull(id).await?.actor()?; let document = self.pull(id).await?.actor()?;
if document.id()? != id { if document.id()? != id {
if let Some(x) = crate::model::actor::Entity::find_by_ap_id(document.id()?).one(tx).await? { if let Some(x) = crate::model::actor::Entity::find_by_ap_id(&document.id()?).one(tx).await? {
return Ok(x); // already in db but we had to follow the "pretty" url, mehh return Ok(x); // already in db but we had to follow the "pretty" url, mehh
} }
} }
@ -381,16 +381,16 @@ impl Fetcher for crate::Context {
} }
async fn resolve_activity(&self, activity: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError> { async fn resolve_activity(&self, activity: serde_json::Value, tx: &impl ConnectionTrait) -> Result<crate::model::activity::Model, RequestError> {
let _domain = self.fetch_domain(&crate::Context::server(activity.id()?), tx).await?; let _domain = self.fetch_domain(&crate::Context::server(&activity.id()?), tx).await?;
if let Ok(activity_actor) = activity.actor().id() { if let Ok(activity_actor) = activity.actor().id() {
if let Err(e) = self.fetch_user(activity_actor, tx).await { if let Err(e) = self.fetch_user(&activity_actor, tx).await {
tracing::warn!("could not get actor of fetched activity: {e}"); tracing::warn!("could not get actor of fetched activity: {e}");
} }
} }
if let Ok(activity_object) = activity.object().id() { if let Ok(activity_object) = activity.object().id() {
if let Err(e) = self.fetch_object(activity_object, tx).await { if let Err(e) = self.fetch_object(&activity_object, tx).await {
tracing::warn!("could not get object of fetched activity: {e}"); tracing::warn!("could not get object of fetched activity: {e}");
} }
} }
@ -403,6 +403,29 @@ impl Fetcher for crate::Context {
async fn fetch_thread(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError> { async fn fetch_thread(&self, id: &str, tx: &impl ConnectionTrait) -> Result<(), RequestError> {
let object = self.pull(id).await?.object()?; let object = self.pull(id).await?.object()?;
// also update object stats since we're pulling it again
let model = self.fetch_object(id, tx).await?;
let mut active = model.clone().into_active_model();
let mut changed = false;
let new_like_count = object.likes_count().unwrap_or_default();
if new_like_count > model.likes {
active.likes = Set(new_like_count);
changed = true;
}
let new_share_count = object.shares_count().unwrap_or_default();
if new_share_count > model.announces {
active.announces = Set(new_share_count);
changed = true;
}
if changed {
active.update(tx).await?;
}
// crawl replies collection
let replies = object.replies().resolve(self).await?; let replies = object.replies().resolve(self).await?;
let mut page; let mut page;
@ -414,17 +437,17 @@ impl Fetcher for crate::Context {
// fix for mastodon: at some point it introduces ?only_other_accounts=true and then returns a // fix for mastodon: at some point it introduces ?only_other_accounts=true and then returns a
// collection, not a page anymore ??? // collection, not a page anymore ???
if matches!(page.object_type()?, apb::ObjectType::Collection(apb::CollectionType::Collection)) { if matches!(page.object_type()?, apb::ObjectType::Collection(apb::CollectionType::Collection)) {
page = page.first().extract().ok_or(RequestError::Tombstone)?; page = page.first().into_inner()?;
} }
for obj in page.items().flat() { for obj in page.items().flat() {
if let Err(e) = self.fetch_object(obj.id()?, tx).await { if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
tracing::warn!("error fetching reply: {e}"); tracing::warn!("error fetching reply: {e}");
} }
} }
for obj in page.ordered_items().flat() { for obj in page.ordered_items().flat() {
if let Err(e) = self.fetch_object(obj.id()?, tx).await { if let Err(e) = self.fetch_object(&obj.id()?, tx).await {
tracing::warn!("error fetching reply: {e}"); tracing::warn!("error fetching reply: {e}");
} }
} }
@ -454,7 +477,7 @@ async fn fetch_object_r(ctx: &crate::Context, id: &str, depth: u32, tx: &impl Co
let object = ctx.pull(id).await?.object()?; let object = ctx.pull(id).await?.object()?;
if object.id()? != id { if object.id()? != id {
if let Some(x) = crate::model::object::Entity::find_by_ap_id(object.id()?).one(tx).await? { if let Some(x) = crate::model::object::Entity::find_by_ap_id(&object.id()?).one(tx).await? {
return Ok(x); // already in db but we had to follow the "pretty" url, mehh return Ok(x); // already in db but we had to follow the "pretty" url, mehh
} }
} }
@ -467,21 +490,21 @@ async fn resolve_object_r(ctx: &crate::Context, object: serde_json::Value, depth
if let Ok(oid) = object.id() { if let Ok(oid) = object.id() {
if oid != id { if oid != id {
if let Some(x) = crate::model::object::Entity::find_by_ap_id(oid).one(tx).await? { if let Some(x) = crate::model::object::Entity::find_by_ap_id(&oid).one(tx).await? {
return Ok(x); // already in db, but with id different that given url return Ok(x); // already in db, but with id different that given url
} }
} }
} }
if let Ok(attributed_to) = object.attributed_to().id() { if let Ok(attributed_to) = object.attributed_to().id() {
if let Err(e) = ctx.fetch_user(attributed_to, tx).await { if let Err(e) = ctx.fetch_user(&attributed_to, tx).await {
tracing::warn!("could not get actor of fetched object: {e}"); tracing::warn!("could not get actor of fetched object: {e}");
} }
} }
if let Ok(reply) = object.in_reply_to().id() { if let Ok(reply) = object.in_reply_to().id() {
if depth <= ctx.cfg().security.thread_crawl_depth { if depth <= ctx.cfg().security.thread_crawl_depth {
fetch_object_r(ctx, reply, depth + 1, tx).await?; fetch_object_r(ctx, &reply, depth + 1, tx).await?;
} else { } else {
tracing::warn!("thread deeper than {}, giving up fetching more replies", ctx.cfg().security.thread_crawl_depth); tracing::warn!("thread deeper than {}, giving up fetching more replies", ctx.cfg().security.thread_crawl_depth);
} }
@ -504,7 +527,7 @@ impl Dereferenceable<serde_json::Value> for apb::Node<serde_json::Value> {
apb::Node::Link(uri) => { apb::Node::Link(uri) => {
let href = uri.href()?; let href = uri.href()?;
tracing::info!("dereferencing {href}"); tracing::info!("dereferencing {href}");
let res = crate::Context::request(Method::GET, href, None, ctx.base(), ctx.pkey(), ctx.domain()) let res = crate::Context::request(Method::GET, &href, None, ctx.base(), ctx.pkey(), ctx.domain())
.await? .await?
.json::<serde_json::Value>() .json::<serde_json::Value>()
.await?; .await?;

View file

@ -1,4 +1,4 @@
use apb::{field::OptionalString, Collection, Document, Endpoints, Node, Object, PublicKey}; use apb::{Document, Endpoints, Node, Object, PublicKey, Shortcuts};
use sea_orm::{sea_query::Expr, ActiveModelTrait, ActiveValue::{Unchanged, NotSet, Set}, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, IntoActiveModel, QueryFilter}; use sea_orm::{sea_query::Expr, ActiveModelTrait, ActiveValue::{Unchanged, NotSet, Set}, ColumnTrait, ConnectionTrait, DbErr, EntityTrait, IntoActiveModel, QueryFilter};
use super::{Cloaker, Fetcher}; use super::{Cloaker, Fetcher};
@ -39,7 +39,7 @@ impl Normalizer for crate::Context {
// > kind of dumb. there should be a job system so this can be done in waves. or maybe there's // > kind of dumb. there should be a job system so this can be done in waves. or maybe there's
// > some whole other way to do this?? im thinking but misskey aaaa!! TODO // > some whole other way to do this?? im thinking but misskey aaaa!! TODO
if let Ok(reply) = object.in_reply_to().id() { if let Ok(reply) = object.in_reply_to().id() {
if let Some(o) = crate::model::object::Entity::find_by_ap_id(reply).one(tx).await? { if let Some(o) = crate::model::object::Entity::find_by_ap_id(&reply).one(tx).await? {
object_model.context = o.context; object_model.context = o.context;
} else { } else {
object_model.context = None; // TODO to be filled by some other task object_model.context = None; // TODO to be filled by some other task
@ -103,7 +103,7 @@ impl Normalizer for crate::Context {
Node::Link(l) => { Node::Link(l) => {
let url = l.href().unwrap_or_default(); let url = l.href().unwrap_or_default();
if url == obj_image { continue }; if url == obj_image { continue };
let mut media_type = l.media_type().unwrap_or("link").to_string(); let mut media_type = l.media_type().unwrap_or("link".to_string());
let mut document_type = apb::DocumentType::Page; let mut document_type = apb::DocumentType::Page;
if self.cfg().compat.fix_attachment_images_media_type if self.cfg().compat.fix_attachment_images_media_type
&& [".jpg", ".jpeg", ".png", ".webp", ".bmp"] // TODO more image types??? && [".jpg", ".jpeg", ".png", ".webp", ".bmp"] // TODO more image types???
@ -116,10 +116,10 @@ impl Normalizer for crate::Context {
} }
crate::model::attachment::ActiveModel { crate::model::attachment::ActiveModel {
internal: sea_orm::ActiveValue::NotSet, internal: sea_orm::ActiveValue::NotSet,
url: Set(self.cloaked(url)), url: Set(self.cloaked(&url)),
object: Set(object_model.internal), object: Set(object_model.internal),
document_type: Set(document_type), document_type: Set(document_type),
name: Set(l.name().str()), name: Set(l.name().ok()),
media_type: Set(media_type), media_type: Set(media_type),
} }
}, },
@ -141,7 +141,7 @@ impl Normalizer for crate::Context {
// we should try to resolve remote users mentioned, otherwise most mentions will // we should try to resolve remote users mentioned, otherwise most mentions will
// be lost. also we shouldn't fail inserting the whole post if the mention fails // be lost. also we shouldn't fail inserting the whole post if the mention fails
// resolving. // resolving.
if let Ok(user) = self.fetch_user(href, tx).await { if let Ok(user) = self.fetch_user(&href, tx).await {
let model = crate::model::mention::ActiveModel { let model = crate::model::mention::ActiveModel {
internal: NotSet, internal: NotSet,
object: Set(object_model.internal), object: Set(object_model.internal),
@ -155,7 +155,7 @@ impl Normalizer for crate::Context {
}, },
Ok(apb::LinkType::Hashtag) => { Ok(apb::LinkType::Hashtag) => {
let hashtag = l.name() let hashtag = l.name()
.unwrap_or_else(|_| l.href().unwrap_or_default().split('/').last().unwrap_or_default()) // TODO maybe just fail? .unwrap_or_else(|_| l.href().unwrap_or_default().split('/').last().unwrap_or_default().to_string()) // TODO maybe just fail?
.replace('#', ""); .replace('#', "");
// TODO lemmy added a "fix" to make its communities kind of work with mastodon: // TODO lemmy added a "fix" to make its communities kind of work with mastodon:
// basically they include the community name as hashtag. ughhhh, since we handle // basically they include the community name as hashtag. ughhhh, since we handle
@ -237,8 +237,8 @@ impl AP {
id: activity.id()?.to_string(), id: activity.id()?.to_string(),
activity_type: activity.activity_type()?, activity_type: activity.activity_type()?,
actor: activity.actor().id()?.to_string(), actor: activity.actor().id()?.to_string(),
object: activity.object().id().str(), object: activity.object().id().ok(),
target: activity.target().id().str(), target: activity.target().id().ok(),
published: activity.published().unwrap_or(chrono::Utc::now()), published: activity.published().unwrap_or(chrono::Utc::now()),
to: activity.to().all_ids().into(), to: activity.to().all_ids().into(),
bto: activity.bto().all_ids().into(), bto: activity.bto().all_ids().into(),
@ -267,11 +267,11 @@ impl AP {
} }
Ok(crate::model::attachment::Model { Ok(crate::model::attachment::Model {
internal: 0, internal: 0,
url: document.url().id().str().unwrap_or_default(), url: document.url().id().unwrap_or_default(),
object: parent, object: parent,
document_type: document.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page)), document_type: document.as_document().map_or(apb::DocumentType::Document, |x| x.document_type().unwrap_or(apb::DocumentType::Page)),
name: document.name().str(), name: document.name().ok(),
media_type: document.media_type().unwrap_or("link").to_string(), media_type: document.media_type().unwrap_or("link".to_string()),
}) })
} }
@ -306,24 +306,21 @@ impl AP {
internal: 0, internal: 0,
id: object.id()?.to_string(), id: object.id()?.to_string(),
object_type: object.object_type()?, object_type: object.object_type()?,
attributed_to: object.attributed_to().id().str(), attributed_to: object.attributed_to().id().ok(),
name: object.name().str(), name: object.name().ok(),
summary: object.summary().str(), summary: object.summary().ok(),
content: object.content().str(), content: object.content().ok(),
image: object.image().get().and_then(|x| x.url().id().str()), image: object.image_url().ok(),
context: object.context().id().str(), context: object.context().id().ok(),
in_reply_to: object.in_reply_to().id().str(), in_reply_to: object.in_reply_to().id().ok(),
quote: object.quote_url().id().str(), quote: object.quote_url().id().ok(),
published: object.published().unwrap_or_else(|_| chrono::Utc::now()), published: object.published().unwrap_or_else(|_| chrono::Utc::now()),
updated: object.updated().unwrap_or_else(|_| chrono::Utc::now()), updated: object.updated().unwrap_or_else(|_| chrono::Utc::now()),
url: object.url().id().str(), url: object.url().id().ok(),
replies: object.replies().get() replies: object.replies_count().unwrap_or_default(),
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32, likes: object.likes_count().unwrap_or_default(),
likes: object.likes().get() announces: object.shares_count().unwrap_or_default(),
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32, audience: object.audience().id().ok(),
announces: object.shares().get()
.map_or(0, |x| x.total_items().unwrap_or(0)) as i32,
audience: object.audience().id().str(),
to: object.to().all_ids().into(), to: object.to().all_ids().into(),
bto: object.bto().all_ids().into(), bto: object.bto().all_ids().into(),
cc: object.cc().all_ids().into(), cc: object.cc().all_ids().into(),
@ -364,30 +361,30 @@ impl AP {
internal: 0, internal: 0,
domain, domain,
id: ap_id, id: ap_id,
preferred_username: actor.preferred_username().unwrap_or(&fallback_preferred_username).to_string(), preferred_username: actor.preferred_username().unwrap_or(fallback_preferred_username).to_string(),
actor_type: actor.actor_type()?, actor_type: actor.actor_type()?,
name: actor.name().str(), name: actor.name().ok(),
summary: actor.summary().str(), summary: actor.summary().ok(),
icon: actor.icon().get().and_then(|x| x.url().id().str()), icon: actor.icon_url().ok(),
image: actor.image().get().and_then(|x| x.url().id().str()), image: actor.image_url().ok(),
inbox: actor.inbox().id().str(), inbox: actor.inbox().id().ok(),
outbox: actor.outbox().id().str(), outbox: actor.outbox().id().ok(),
shared_inbox: actor.endpoints().get().and_then(|x| x.shared_inbox().str()), shared_inbox: actor.endpoints().inner().and_then(|x| x.shared_inbox()).map(|x| x.to_string()).ok(),
followers: actor.followers().id().str(), followers: actor.followers().id().ok(),
following: actor.following().id().str(), following: actor.following().id().ok(),
also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().str()).collect::<Vec<String>>().into(), also_known_as: actor.also_known_as().flat().into_iter().filter_map(|x| x.id().ok()).collect::<Vec<String>>().into(),
moved_to: actor.moved_to().id().str(), moved_to: actor.moved_to().id().ok(),
published: actor.published().unwrap_or(chrono::Utc::now()), published: actor.published().unwrap_or(chrono::Utc::now()),
updated: chrono::Utc::now(), updated: chrono::Utc::now(),
following_count: actor.following_count().unwrap_or(0) as i32, following_count: actor.following_count().unwrap_or(0) as i32,
followers_count: actor.followers_count().unwrap_or(0) as i32, followers_count: actor.followers_count().unwrap_or(0) as i32,
statuses_count: actor.statuses_count().unwrap_or(0) as i32, statuses_count: actor.statuses_count().unwrap_or(0) as i32,
public_key: actor.public_key().get().ok_or(apb::FieldErr("publicKey"))?.public_key_pem().to_string(), public_key: actor.public_key().inner()?.public_key_pem().to_string(),
private_key: None, // there's no way to transport privkey over AP json, must come from DB private_key: None, // there's no way to transport privkey over AP json, must come from DB
fields: actor.attachment() fields: actor.attachment()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| Some(crate::model::actor::Field::from(x.extract()?))) .filter_map(|x| Some(crate::model::actor::Field::from(x.into_inner().ok()?)))
.collect::<Vec<crate::model::actor::Field>>() .collect::<Vec<crate::model::actor::Field>>()
.into(), .into(),
}) })

View file

@ -55,19 +55,19 @@ impl Processor for crate::Context {
} }
pub async fn create(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn create(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let Some(object_node) = activity.object().extract() else { let Ok(object_node) = activity.object().into_inner() else {
// TODO we could process non-embedded activities or arrays but im lazy rn // TODO we could process non-embedded activities or arrays but im lazy rn
tracing::error!("refusing to process activity without embedded object"); tracing::error!("refusing to process activity without embedded object");
return Err(ProcessorError::Unprocessable(activity.id()?.to_string())); return Err(ProcessorError::Unprocessable(activity.id()?.to_string()));
}; };
if model::object::Entity::ap_to_internal(object_node.id()?, tx).await?.is_some() { if model::object::Entity::ap_to_internal(&object_node.id()?, tx).await?.is_some() {
return Err(ProcessorError::AlreadyProcessed); return Err(ProcessorError::AlreadyProcessed);
} }
if object_node.attributed_to().id()? != activity.actor().id()? { if object_node.attributed_to().id()? != activity.actor().id()? {
return Err(ProcessorError::Unauthorized); return Err(ProcessorError::Unauthorized);
} }
if let Ok(reply) = object_node.in_reply_to().id() { if let Ok(reply) = object_node.in_reply_to().id() {
if let Err(e) = ctx.fetch_object(reply, tx).await { if let Err(e) = ctx.fetch_object(&reply, tx).await {
tracing::warn!("failed fetching replies for received object: {e}"); tracing::warn!("failed fetching replies for received object: {e}");
} }
} }
@ -96,8 +96,8 @@ pub async fn create(ctx: &crate::Context, activity: impl apb::Activity, tx: &Dat
} }
pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let actor = ctx.fetch_user(activity.actor().id()?, tx).await?; let actor = ctx.fetch_user(&activity.actor().id()?, tx).await?;
let obj = ctx.fetch_object(activity.object().id()?, tx).await?; let obj = ctx.fetch_object(&activity.object().id()?, tx).await?;
let likes_local_object = obj.attributed_to.as_ref().map(|x| ctx.is_local(x)).unwrap_or_default(); let likes_local_object = obj.attributed_to.as_ref().map(|x| ctx.is_local(x)).unwrap_or_default();
if crate::model::like::Entity::find_by_uid_oid(actor.internal, obj.internal) if crate::model::like::Entity::find_by_uid_oid(actor.internal, obj.internal)
.any(tx) .any(tx)
@ -146,8 +146,8 @@ pub async fn like(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab
// TODO basically same as like, can we make one function, maybe with const generic??? // TODO basically same as like, can we make one function, maybe with const generic???
pub async fn dislike(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn dislike(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let actor = ctx.fetch_user(activity.actor().id()?, tx).await?; let actor = ctx.fetch_user(&activity.actor().id()?, tx).await?;
let obj = ctx.fetch_object(activity.object().id()?, tx).await?; let obj = ctx.fetch_object(&activity.object().id()?, tx).await?;
if crate::model::dislike::Entity::find_by_uid_oid(actor.internal, obj.internal) if crate::model::dislike::Entity::find_by_uid_oid(actor.internal, obj.internal)
.any(tx) .any(tx)
.await? .await?
@ -186,11 +186,11 @@ pub async fn dislike(ctx: &crate::Context, activity: impl apb::Activity, tx: &Da
} }
pub async fn follow(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn follow(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
let source_actor = crate::model::actor::Entity::find_by_ap_id(activity.actor().id()?) let source_actor = crate::model::actor::Entity::find_by_ap_id(&activity.actor().id()?)
.one(tx) .one(tx)
.await? .await?
.ok_or(ProcessorError::Incomplete)?; .ok_or(ProcessorError::Incomplete)?;
let target_actor = ctx.fetch_user(activity.object().id()?, tx).await?; let target_actor = ctx.fetch_user(&activity.object().id()?, tx).await?;
let activity_model = ctx.insert_activity(activity, tx).await?; let activity_model = ctx.insert_activity(activity, tx).await?;
ctx.address(Some(&activity_model), None, tx).await?; ctx.address(Some(&activity_model), None, tx).await?;
@ -246,7 +246,7 @@ pub async fn follow(ctx: &crate::Context, activity: impl apb::Activity, tx: &Dat
pub async fn accept(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn accept(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
// TODO what about TentativeAccept // TODO what about TentativeAccept
let follow_activity = crate::model::activity::Entity::find_by_ap_id(activity.object().id()?) let follow_activity = crate::model::activity::Entity::find_by_ap_id(&activity.object().id()?)
.one(tx) .one(tx)
.await? .await?
.ok_or(ProcessorError::Incomplete)?; .ok_or(ProcessorError::Incomplete)?;
@ -305,7 +305,7 @@ pub async fn accept(ctx: &crate::Context, activity: impl apb::Activity, tx: &Dat
pub async fn reject(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn reject(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
// TODO what about TentativeReject? // TODO what about TentativeReject?
let follow_activity = crate::model::activity::Entity::find_by_ap_id(activity.object().id()?) let follow_activity = crate::model::activity::Entity::find_by_ap_id(&activity.object().id()?)
.one(tx) .one(tx)
.await? .await?
.ok_or(ProcessorError::Incomplete)?; .ok_or(ProcessorError::Incomplete)?;
@ -356,7 +356,7 @@ pub async fn delete(ctx: &crate::Context, activity: impl apb::Activity, tx: &Dat
pub async fn update(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn update(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
// TODO when attachments get updated we do nothing!!!!!!!!!! // TODO when attachments get updated we do nothing!!!!!!!!!!
let Some(object_node) = activity.object().extract() else { let Ok(object_node) = activity.object().into_inner() else {
tracing::error!("refusing to process activity without embedded object"); tracing::error!("refusing to process activity without embedded object");
return Err(ProcessorError::Unprocessable(activity.id()?.to_string())); return Err(ProcessorError::Unprocessable(activity.id()?.to_string()));
}; };
@ -415,9 +415,7 @@ pub async fn update(ctx: &crate::Context, activity: impl apb::Activity, tx: &Dat
pub async fn undo(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> { pub async fn undo(ctx: &crate::Context, activity: impl apb::Activity, tx: &DatabaseTransaction) -> Result<(), ProcessorError> {
// TODO in theory we could work with just object_id but right now only accept embedded // TODO in theory we could work with just object_id but right now only accept embedded
let undone_activity = activity.object() let undone_activity = activity.object().into_inner()?;
.extract()
.ok_or(apb::FieldErr("object"))?;
let uid = activity.actor().id()?.to_string(); let uid = activity.actor().id()?.to_string();
let internal_uid = crate::model::actor::Entity::ap_to_internal(&uid, tx) let internal_uid = crate::model::actor::Entity::ap_to_internal(&uid, tx)
@ -431,7 +429,7 @@ pub async fn undo(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab
match undone_activity.as_activity()?.activity_type()? { match undone_activity.as_activity()?.activity_type()? {
apb::ActivityType::Like => { apb::ActivityType::Like => {
let internal_oid = crate::model::object::Entity::ap_to_internal( let internal_oid = crate::model::object::Entity::ap_to_internal(
undone_activity.as_activity()?.object().id()?, &undone_activity.as_activity()?.object().id()?,
tx tx
) )
.await? .await?
@ -452,7 +450,7 @@ pub async fn undo(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab
}, },
apb::ActivityType::Follow => { apb::ActivityType::Follow => {
let internal_uid_following = crate::model::actor::Entity::ap_to_internal( let internal_uid_following = crate::model::actor::Entity::ap_to_internal(
undone_activity.as_activity()?.object().id()?, &undone_activity.as_activity()?.object().id()?,
tx, tx,
) )
.await? .await?
@ -496,7 +494,7 @@ pub async fn undo(ctx: &crate::Context, activity: impl apb::Activity, tx: &Datab
ctx.address(Some(&activity_model), None, tx).await?; ctx.address(Some(&activity_model), None, tx).await?;
} }
if let Some(internal) = crate::model::activity::Entity::ap_to_internal(undone_activity.id()?, tx).await? { if let Some(internal) = crate::model::activity::Entity::ap_to_internal(&undone_activity.id()?, tx).await? {
crate::model::notification::Entity::delete_many() crate::model::notification::Entity::delete_many()
.filter(crate::model::notification::Column::Activity.eq(internal)) .filter(crate::model::notification::Column::Activity.eq(internal))
.exec(tx) .exec(tx)
@ -534,7 +532,7 @@ pub async fn announce(ctx: &crate::Context, activity: impl apb::Activity, tx: &D
} }
}; };
let actor = ctx.fetch_user(activity.actor().id()?, tx).await?; let actor = ctx.fetch_user(&activity.actor().id()?, tx).await?;
// we only care about announces produced by "Person" actors, because there's intention // we only care about announces produced by "Person" actors, because there's intention
// anything shared by groups, services or applications is automated: fetch it and be done // anything shared by groups, services or applications is automated: fetch it and be done

View file

@ -47,7 +47,7 @@ pub async fn get<const OUTGOING: bool>(
}; };
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/{follow___}"), Some(count as u64)) crate::builders::collection(upub::url!(ctx, "/actors/{id}/{follow___}"), Some(count as u64))
} }
pub async fn page<const OUTGOING: bool>( pub async fn page<const OUTGOING: bool>(

View file

@ -14,7 +14,7 @@ pub async fn get(
Identity::Anonymous => Err(crate::ApiError::forbidden()), Identity::Anonymous => Err(crate::ApiError::forbidden()),
Identity::Remote { .. } => Err(crate::ApiError::forbidden()), Identity::Remote { .. } => Err(crate::ApiError::forbidden()),
Identity::Local { id: user, .. } => if ctx.uid(&id) == user { Identity::Local { id: user, .. } => if ctx.uid(&id) == user {
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/inbox"), None) crate::builders::collection(upub::url!(ctx, "/actors/{id}/inbox"), None)
} else { } else {
Err(crate::ApiError::forbidden()) Err(crate::ApiError::forbidden())
}, },

View file

@ -65,8 +65,8 @@ pub async fn view(
.set_manually_approves_followers(Some(!cfg.accept_follow_requests)) .set_manually_approves_followers(Some(!cfg.accept_follow_requests))
.set_endpoints(Node::object( .set_endpoints(Node::object(
apb::new() apb::new()
.set_shared_inbox(Some(&upub::url!(ctx, "/inbox"))) .set_shared_inbox(Some(upub::url!(ctx, "/inbox")))
.set_proxy_url(Some(&upub::url!(ctx, "/fetch"))) .set_proxy_url(Some(upub::url!(ctx, "/fetch")))
)); ));
if auth.is(&uid) { if auth.is(&uid) {

View file

@ -22,7 +22,7 @@ pub async fn get(
.count(ctx.db()) .count(ctx.db())
.await?; .await?;
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/notifications"), Some(count)) crate::builders::collection(upub::url!(ctx, "/actors/{id}/notifications"), Some(count))
} }
pub async fn page( pub async fn page(

View file

@ -9,7 +9,7 @@ pub async fn get(
State(ctx): State<Context>, State(ctx): State<Context>,
Path(id): Path<String>, Path(id): Path<String>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/outbox"), None) crate::builders::collection(upub::url!(ctx, "/actors/{id}/outbox"), None)
} }
pub async fn page( pub async fn page(

View file

@ -22,21 +22,21 @@ pub async fn view(
} }
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(&upub::url!(ctx, ""))) .set_id(Some(upub::url!(ctx, "")))
.set_actor_type(Some(apb::ActorType::Application)) .set_actor_type(Some(apb::ActorType::Application))
.set_name(Some(&ctx.cfg().instance.name)) .set_name(Some(ctx.cfg().instance.name.clone()))
.set_summary(Some(&ctx.cfg().instance.description)) .set_summary(Some(ctx.cfg().instance.description.clone()))
.set_inbox(apb::Node::link(upub::url!(ctx, "/inbox"))) .set_inbox(apb::Node::link(upub::url!(ctx, "/inbox")))
.set_outbox(apb::Node::link(upub::url!(ctx, "/outbox"))) .set_outbox(apb::Node::link(upub::url!(ctx, "/outbox")))
.set_published(Some(ctx.actor().published)) .set_published(Some(ctx.actor().published))
.set_endpoints(apb::Node::Empty) .set_endpoints(apb::Node::Empty)
.set_preferred_username(Some(ctx.domain())) .set_preferred_username(Some(ctx.domain().to_string()))
.set_url(apb::Node::link(upub::url!(ctx, "/"))) .set_url(apb::Node::link(upub::url!(ctx, "/")))
.set_public_key(apb::Node::object( .set_public_key(apb::Node::object(
apb::new() apb::new()
.set_id(Some(&upub::url!(ctx, "#main-key"))) .set_id(Some(upub::url!(ctx, "#main-key")))
.set_owner(Some(&upub::url!(ctx, ""))) .set_owner(Some(upub::url!(ctx, "")))
.set_public_key_pem(&ctx.actor().public_key) .set_public_key_pem(ctx.actor().public_key.clone())
)) ))
.ld_context() .ld_context()
).into_response()) ).into_response())

View file

@ -14,7 +14,11 @@ pub async fn upload(
} }
let mut uploaded_something = false; let mut uploaded_something = false;
while let Some(field) = multipart.next_field().await.unwrap() { while let Some(field) = multipart
.next_field()
.await
.unwrap() // TODO OOOPS THIS SLIPPED GET RID OF IT
{
let _ = if let Some(filename) = field.file_name() { let _ = if let Some(filename) = field.file_name() {
filename.to_string() filename.to_string()
} else { } else {

View file

@ -11,7 +11,7 @@ use super::Pagination;
pub async fn get( pub async fn get(
State(ctx): State<Context>, State(ctx): State<Context>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(&upub::url!(ctx, "/inbox"), None) crate::builders::collection(upub::url!(ctx, "/inbox"), None)
} }
pub async fn page( pub async fn page(

View file

@ -17,7 +17,7 @@ pub async fn get(
.count(ctx.db()) .count(ctx.db())
.await?; .await?;
crate::builders::collection(&upub::url!(ctx, "/objects/{id}/context"), Some(count)) crate::builders::collection(upub::url!(ctx, "/objects/{id}/context"), Some(count))
} }
pub async fn page( pub async fn page(

View file

@ -55,7 +55,7 @@ pub async fn view(
replies = apb::Node::object( replies = apb::Node::object(
apb::new() apb::new()
.set_id(Some(&upub::url!(ctx, "/objects/{id}/replies"))) .set_id(Some(upub::url!(ctx, "/objects/{id}/replies")))
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page"))) .set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page")))
.set_collection_type(Some(apb::CollectionType::Collection)) .set_collection_type(Some(apb::CollectionType::Collection))
.set_total_items(item.object.as_ref().map(|x| x.replies as u64)) .set_total_items(item.object.as_ref().map(|x| x.replies as u64))

View file

@ -32,7 +32,7 @@ pub async fn get(
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(&upub::url!(ctx, "/objects/{id}/replies"))) .set_id(Some(upub::url!(ctx, "/objects/{id}/replies")))
.set_collection_type(Some(apb::CollectionType::Collection)) .set_collection_type(Some(apb::CollectionType::Collection))
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page"))) .set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page")))
.set_total_items(Some(replies_ids.len() as u64)) .set_total_items(Some(replies_ids.len() as u64))

View file

@ -5,7 +5,7 @@ use upub::Context;
use crate::{activitypub::{CreationResult, Pagination}, AuthIdentity, builders::JsonLD}; use crate::{activitypub::{CreationResult, Pagination}, AuthIdentity, builders::JsonLD};
pub async fn get(State(ctx): State<Context>) -> crate::ApiResult<JsonLD<serde_json::Value>> { pub async fn get(State(ctx): State<Context>) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(&upub::url!(ctx, "/outbox"), None) crate::builders::collection(upub::url!(ctx, "/outbox"), None)
} }
pub async fn page( pub async fn page(

View file

@ -10,7 +10,7 @@ pub async fn get(
Path(id): Path<String>, Path(id): Path<String>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection( crate::builders::collection(
&upub::url!(ctx, "/tags/{id}"), upub::url!(ctx, "/tags/{id}"),
None, None,
) )
} }

View file

@ -63,7 +63,7 @@ pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json:
}; };
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(&format!("{id}?offset={offset}"))) .set_id(Some(format!("{id}?offset={offset}")))
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage)) .set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
.set_part_of(apb::Node::link(id.replace("/page", ""))) .set_part_of(apb::Node::link(id.replace("/page", "")))
.set_ordered_items(apb::Node::array(items)) .set_ordered_items(apb::Node::array(items))
@ -73,13 +73,13 @@ pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json:
} }
pub fn collection(id: &str, total_items: Option<u64>) -> crate::ApiResult<JsonLD<serde_json::Value>> { pub fn collection(id: String, total_items: Option<u64>) -> crate::ApiResult<JsonLD<serde_json::Value>> {
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(id))
.set_collection_type(Some(apb::CollectionType::OrderedCollection)) .set_collection_type(Some(apb::CollectionType::OrderedCollection))
.set_first(apb::Node::link(format!("{id}/page"))) .set_first(apb::Node::link(format!("{id}/page")))
.set_total_items(total_items) .set_total_items(total_items)
.set_id(Some(id))
.ld_context() .ld_context()
)) ))
} }

View file

@ -6,7 +6,7 @@ pub enum ApiError {
Database(#[from] sea_orm::DbErr), Database(#[from] sea_orm::DbErr),
#[error("encountered malformed object: {0}")] #[error("encountered malformed object: {0}")]
Field(#[from] apb::FieldErr), Malformed(#[from] apb::FieldErr),
#[error("http signature error: {0:?}")] #[error("http signature error: {0:?}")]
HttpSignature(#[from] httpsign::HttpSignatureError), HttpSignature(#[from] httpsign::HttpSignatureError),
@ -104,7 +104,7 @@ impl axum::response::IntoResponse for ApiError {
"inner": format!("{pull:#?}"), "inner": format!("{pull:#?}"),
})) }))
).into_response(), ).into_response(),
ApiError::Field(x) => ( ApiError::Malformed(x) => (
axum::http::StatusCode::BAD_REQUEST, axum::http::StatusCode::BAD_REQUEST,
axum::Json(serde_json::json!({ axum::Json(serde_json::json!({
"error": "field", "error": "field",

View file

@ -1,4 +1,4 @@
use apb::{field::OptionalString, target::Addressed, Activity, ActivityMut, Base, BaseMut, Object, ObjectMut}; use apb::{target::Addressed, Activity, ActivityMut, Base, BaseMut, Object, ObjectMut, Shortcuts};
use sea_orm::{prelude::Expr, ColumnTrait, DbErr, EntityTrait, QueryFilter, QueryOrder, QuerySelect, SelectColumns, TransactionTrait}; use sea_orm::{prelude::Expr, ColumnTrait, DbErr, EntityTrait, QueryFilter, QueryOrder, QuerySelect, SelectColumns, TransactionTrait};
use upub::{model::{self, actor::Field}, traits::{process::ProcessorError, Addresser, Processor}, Context}; use upub::{model::{self, actor::Field}, traits::{process::ProcessorError, Addresser, Processor}, Context};
@ -17,7 +17,7 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
let actor = upub::model::actor::Entity::ap_to_internal(&job.actor, &tx) let actor = upub::model::actor::Entity::ap_to_internal(&job.actor, &tx)
.await? .await?
.ok_or_else(|| DbErr::RecordNotFound(job.actor.clone()))?; .ok_or_else(|| DbErr::RecordNotFound(job.actor.clone()))?;
let activity = upub::model::activity::Entity::ap_to_internal(activity.object().id()?, &tx) let activity = upub::model::activity::Entity::ap_to_internal(&activity.object().id()?, &tx)
.await? .await?
.ok_or_else(|| DbErr::RecordNotFound(activity.object().id().unwrap_or_default().to_string()))?; .ok_or_else(|| DbErr::RecordNotFound(activity.object().id().unwrap_or_default().to_string()))?;
upub::model::notification::Entity::update_many() upub::model::notification::Entity::update_many()
@ -41,14 +41,14 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
} }
activity = activity activity = activity
.set_id(Some(&job.activity)) .set_id(Some(job.activity.clone()))
.set_actor(apb::Node::link(job.actor.clone())) .set_actor(apb::Node::link(job.actor.clone()))
.set_published(Some(now)); .set_published(Some(now));
if matches!(t, apb::ObjectType::Activity(apb::ActivityType::Undo)) { if matches!(t, apb::ObjectType::Activity(apb::ActivityType::Undo)) {
let mut undone = activity.object().extract().ok_or(crate::JobError::MissingPayload)?; let mut undone = activity.object().into_inner()?;
if undone.id().is_err() { if undone.id().is_err() {
let undone_target = undone.object().id().str().ok_or(crate::JobError::MissingPayload)?; let undone_target = undone.object().id()?;
let undone_type = undone.activity_type().map_err(|_| crate::JobError::MissingPayload)?; let undone_type = undone.activity_type().map_err(|_| crate::JobError::MissingPayload)?;
let undone_model = model::activity::Entity::find() let undone_model = model::activity::Entity::find()
.filter(model::activity::Column::Object.eq(&undone_target)) .filter(model::activity::Column::Object.eq(&undone_target))
@ -59,7 +59,7 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
.await? .await?
.ok_or_else(|| sea_orm::DbErr::RecordNotFound(format!("actor={},type={},object={}",job.actor, undone_type, undone_target)))?; .ok_or_else(|| sea_orm::DbErr::RecordNotFound(format!("actor={},type={},object={}",job.actor, undone_type, undone_target)))?;
undone = undone undone = undone
.set_id(Some(&undone_model.id)) .set_id(Some(undone_model.id))
.set_actor(apb::Node::link(job.actor.clone())); .set_actor(apb::Node::link(job.actor.clone()));
} }
activity = activity.set_object(apb::Node::object(undone)); activity = activity.set_object(apb::Node::object(undone));
@ -67,17 +67,17 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
macro_rules! update { macro_rules! update {
($prev:ident, $field:ident, $getter:expr) => { ($prev:ident, $field:ident, $getter:expr) => {
if let Some($field) = $getter { if let Ok($field) = $getter {
$prev.$field = Some($field.to_string()); $prev.$field = Some($field.to_string());
} }
}; };
} }
if matches!(t, apb::ObjectType::Activity(apb::ActivityType::Update)) { if matches!(t, apb::ObjectType::Activity(apb::ActivityType::Update)) {
let mut updated = activity.object().extract().ok_or(crate::JobError::MissingPayload)?; let mut updated = activity.object().into_inner()?;
match updated.object_type()? { match updated.object_type()? {
apb::ObjectType::Actor(_) => { apb::ObjectType::Actor(_) => {
let mut prev = model::actor::Entity::find_by_ap_id(updated.id()?) let mut prev = model::actor::Entity::find_by_ap_id(&updated.id()?)
.one(&tx) .one(&tx)
.await? .await?
.ok_or_else(|| crate::JobError::MissingPayload)?; .ok_or_else(|| crate::JobError::MissingPayload)?;
@ -86,16 +86,16 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
return Err(crate::JobError::Forbidden); return Err(crate::JobError::Forbidden);
} }
update!(prev, name, updated.name().ok()); update!(prev, name, updated.name());
update!(prev, summary, updated.summary().ok()); update!(prev, summary, updated.summary());
update!(prev, icon, updated.icon().get().and_then(|x| x.url().id().str())); update!(prev, icon, updated.icon_url());
update!(prev, image, updated.image().get().and_then(|x| x.url().id().str())); update!(prev, image, updated.image_url());
if !updated.attachment().is_empty() { if !updated.attachment().is_empty() {
prev.fields = updated.attachment() prev.fields = updated.attachment()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| x.extract()) .filter_map(|x| x.into_inner().ok())
.map(Field::from) .map(Field::from)
.collect::<Vec<Field>>() .collect::<Vec<Field>>()
.into(); .into();
@ -104,7 +104,7 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
updated = prev.ap(); updated = prev.ap();
}, },
apb::ObjectType::Note => { apb::ObjectType::Note => {
let mut prev = model::object::Entity::find_by_ap_id(updated.id()?) let mut prev = model::object::Entity::find_by_ap_id(&updated.id()?)
.one(&tx) .one(&tx)
.await? .await?
.ok_or_else(|| crate::JobError::MissingPayload)?; .ok_or_else(|| crate::JobError::MissingPayload)?;
@ -113,10 +113,10 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
return Err(crate::JobError::Forbidden); return Err(crate::JobError::Forbidden);
} }
update!(prev, name, updated.name().ok()); update!(prev, name, updated.name());
update!(prev, summary, updated.summary().ok()); update!(prev, summary, updated.summary());
update!(prev, content, updated.content().ok()); update!(prev, content, updated.content());
update!(prev, image, updated.image().get().and_then(|x| x.url().id().str())); update!(prev, image, updated.image_url());
if let Ok(sensitive) = updated.sensitive() { if let Ok(sensitive) = updated.sensitive() {
prev.sensitive = sensitive; prev.sensitive = sensitive;
@ -133,7 +133,7 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
let raw_oid = Context::new_id(); let raw_oid = Context::new_id();
let oid = ctx.oid(&raw_oid); let oid = ctx.oid(&raw_oid);
// object must be embedded, wont dereference here // object must be embedded, wont dereference here
let object = activity.object().extract().ok_or(apb::FieldErr("object"))?; let object = activity.object().into_inner()?;
// TODO regex hell here i come... // TODO regex hell here i come...
let re = regex::Regex::new(r"@(.+)@([^ ]+)").expect("failed compiling regex pattern"); let re = regex::Regex::new(r"@(.+)@([^ ]+)").expect("failed compiling regex pattern");
let mut content = object.content().map(|x| x.to_string()).ok(); let mut content = object.content().map(|x| x.to_string()).ok();
@ -158,8 +158,8 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
activity = activity activity = activity
.set_object(apb::Node::object( .set_object(apb::Node::object(
object object
.set_id(Some(&oid)) .set_id(Some(oid))
.set_content(content.as_deref()) .set_content(content)
.set_attributed_to(apb::Node::link(job.actor.clone())) .set_attributed_to(apb::Node::link(job.actor.clone()))
.set_published(Some(now)) .set_published(Some(now))
.set_updated(Some(now)) .set_updated(Some(now))

View file

@ -1,16 +1,16 @@
use leptos::*; use leptos::*;
use crate::prelude::*; use crate::prelude::*;
use apb::{field::OptionalString, target::Addressed, Activity, ActivityMut, Base, Object}; use apb::{target::Addressed, Activity, ActivityMut, Base, Object};
#[component] #[component]
pub fn ActivityLine(activity: crate::Object, children: Children) -> impl IntoView { pub fn ActivityLine(activity: crate::Object, children: Children) -> impl IntoView {
let object_id = activity.object().id().str().unwrap_or_default(); let object_id = activity.object().id().unwrap_or_default();
let activity_url = activity.id().map(|x| view! { let activity_url = activity.id().map(|x| view! {
<sup><small><a class="clean ml-s" href={x.to_string()} target="_blank">""</a></small></sup> <sup><small><a class="clean ml-s" href={x.to_string()} target="_blank">""</a></small></sup>
}); });
let actor_id = activity.actor().id().str().unwrap_or_default(); let actor_id = activity.actor().id().unwrap_or_default();
let actor = cache::OBJECTS.get_or(&actor_id, serde_json::Value::String(actor_id.clone()).into()); let actor = cache::OBJECTS.get_or(&actor_id, serde_json::Value::String(actor_id.clone()).into());
let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity); let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity);
let href = match kind { let href = match kind {
@ -60,7 +60,7 @@ pub fn Item(
Some(view! { <Object object=item.clone() />{sep.clone()} }.into_view()), Some(view! { <Object object=item.clone() />{sep.clone()} }.into_view()),
// everything else // everything else
apb::ObjectType::Activity(t) => { apb::ObjectType::Activity(t) => {
let object_id = item.object().id().str().unwrap_or_default(); let object_id = item.object().id().unwrap_or_default();
let object = match t { let object = match t {
apb::ActivityType::Create | apb::ActivityType::Announce => apb::ActivityType::Create | apb::ActivityType::Announce =>
cache::OBJECTS.get(&object_id).map(|obj| { cache::OBJECTS.get(&object_id).map(|obj| {

View file

@ -2,7 +2,7 @@ use leptos::*;
use leptos_router::*; use leptos_router::*;
use crate::{getters::Getter, prelude::*, FALLBACK_IMAGE_URL}; use crate::{getters::Getter, prelude::*, FALLBACK_IMAGE_URL};
use apb::{field::OptionalString, ActivityMut, Actor, Base, Object, ObjectMut}; use apb::{ActivityMut, Actor, Base, Object, ObjectMut, Shortcuts};
#[component] #[component]
pub fn ActorHeader() -> impl IntoView { pub fn ActorHeader() -> impl IntoView {
@ -16,7 +16,7 @@ pub fn ActorHeader() -> impl IntoView {
Some(x) => Some(x.clone()), Some(x) => Some(x.clone()),
None => { None => {
let user = cache::OBJECTS.resolve(&id, U::Actor, auth).await?; let user = cache::OBJECTS.resolve(&id, U::Actor, auth).await?;
if let Some(url) = user.url().id().str() { if let Ok(url) = user.url().id() {
cache::WEBFINGER.store(&url, user.id().unwrap_or_default().to_string()); cache::WEBFINGER.store(&url, user.id().unwrap_or_default().to_string());
} }
Some(user) Some(user)
@ -29,10 +29,10 @@ pub fn ActorHeader() -> impl IntoView {
None => view! { <Loader /> }.into_view(), None => view! { <Loader /> }.into_view(),
Some(None) => view! { <code class="center cw color">"could not resolve user"</code> }.into_view(), Some(None) => view! { <code class="center cw color">"could not resolve user"</code> }.into_view(),
Some(Some(actor)) => { Some(Some(actor)) => {
let avatar_url = actor.icon().get().map(|x| x.url().id().str().unwrap_or(FALLBACK_IMAGE_URL.into())).unwrap_or(FALLBACK_IMAGE_URL.into()); let avatar_url = actor.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into());
let background_url = actor.image().get().map(|x| x.url().id().str().unwrap_or(FALLBACK_IMAGE_URL.into())).unwrap_or(FALLBACK_IMAGE_URL.into()); let background_url = actor.image_url().unwrap_or(FALLBACK_IMAGE_URL.into());
let username = actor.preferred_username().unwrap_or_default().to_string(); let username = actor.preferred_username().unwrap_or_default().to_string();
let name = actor.name().str().unwrap_or(username.clone()); let name = actor.name().unwrap_or(username.clone());
let created = actor.published().ok(); let created = actor.published().ok();
let following_me = actor.following_me().unwrap_or(false); let following_me = actor.following_me().unwrap_or(false);
let followed_by_me = actor.followed_by_me().unwrap_or(false); let followed_by_me = actor.followed_by_me().unwrap_or(false);
@ -44,11 +44,11 @@ pub fn ActorHeader() -> impl IntoView {
let fields = actor.attachment() let fields = actor.attachment()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| x.extract()) .filter_map(|x| x.into_inner().ok())
.map(|x| view! { .map(|x| view! {
<tr> <tr>
<td class="w-25"><b class="color">{x.name().str().unwrap_or_default()}</b></td> <td class="w-25"><b class="color">{x.name().unwrap_or_default()}</b></td>
<td class="w-75" inner_html={mdhtml::safe_html(x.value().unwrap_or_default())}></td> <td class="w-75" inner_html={mdhtml::safe_html(&x.value().unwrap_or_default())}></td>
</tr> </tr>
}) })
.collect_view(); .collect_view();
@ -110,7 +110,7 @@ pub fn ActorHeader() -> impl IntoView {
}} }}
</div> </div>
</div> </div>
<p class="mb-2 mt-0 center bio" inner_html={mdhtml::safe_html(actor.summary().unwrap_or_default())}></p> <p class="mb-2 mt-0 center bio" inner_html={mdhtml::safe_html(&actor.summary().unwrap_or_default())}></p>
<p class="center"> <p class="center">
<table class="fields center w-100 pa-s" style="margin: auto; table-layout: fixed;">{fields}</table> <table class="fields center w-100 pa-s" style="margin: auto; table-layout: fixed;">{fields}</table>
</p> </p>

View file

@ -1,4 +1,4 @@
use apb::{field::OptionalString, ActivityMut, Base, BaseMut, Object, ObjectMut}; use apb::{ActivityMut, Base, BaseMut, Object, ObjectMut};
use leptos::*; use leptos::*;
use crate::prelude::*; use crate::prelude::*;
@ -12,7 +12,7 @@ pub struct ReplyControls {
impl ReplyControls { impl ReplyControls {
pub fn reply(&self, oid: &str) { pub fn reply(&self, oid: &str) {
if let Some(obj) = cache::OBJECTS.get(oid) { if let Some(obj) = cache::OBJECTS.get(oid) {
self.context.set(obj.context().id().str()); self.context.set(obj.context().id().ok());
self.reply_to.set(obj.id().ok().map(|x| x.to_string())); self.reply_to.set(obj.id().ok().map(|x| x.to_string()));
} }
} }
@ -24,7 +24,7 @@ impl ReplyControls {
} }
fn post_author(post_id: &str) -> Option<crate::Object> { fn post_author(post_id: &str) -> Option<crate::Object> {
let usr = cache::OBJECTS.get(post_id)?.attributed_to().id().str()?; let usr = cache::OBJECTS.get(post_id)?.attributed_to().id().ok()?;
cache::OBJECTS.get(&usr) cache::OBJECTS.get(&usr)
} }
@ -135,9 +135,9 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
.into_iter() .into_iter()
.map(|x| { .map(|x| {
use apb::LinkMut; use apb::LinkMut;
LinkMut::set_name(apb::new(), Some(&format!("@{}@{}", x.name, x.domain))) // TODO ewww but name clashes LinkMut::set_name(apb::new(), Some(format!("@{}@{}", x.name, x.domain))) // TODO ewww but name clashes
.set_link_type(Some(apb::LinkType::Mention)) .set_link_type(Some(apb::LinkType::Mention))
.set_href(Some(&x.href)) .set_href(Some(x.href))
}) })
.collect(); .collect();
@ -146,10 +146,10 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
if let Ok(uid) = au.id() { if let Ok(uid) = au.id() {
to_vec.push(uid.to_string()); to_vec.push(uid.to_string());
if let Ok(name) = au.name() { if let Ok(name) = au.name() {
let domain = Uri::domain(uid); let domain = Uri::domain(&uid);
mention_tags.push({ mention_tags.push({
use apb::LinkMut; use apb::LinkMut;
LinkMut::set_name(apb::new(), Some(&format!("@{}@{}", name, domain))) // TODO ewww but name clashes LinkMut::set_name(apb::new(), Some(format!("@{}@{}", name, domain))) // TODO ewww but name clashes
.set_link_type(Some(apb::LinkType::Mention)) .set_link_type(Some(apb::LinkType::Mention))
.set_href(Some(uid)) .set_href(Some(uid))
}); });
@ -162,8 +162,8 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
} }
let payload = apb::new() let payload = apb::new()
.set_object_type(Some(apb::ObjectType::Note)) .set_object_type(Some(apb::ObjectType::Note))
.set_summary(summary.as_deref()) .set_summary(summary)
.set_content(Some(&content)) .set_content(Some(content))
.set_context(apb::Node::maybe_link(reply.context.get())) .set_context(apb::Node::maybe_link(reply.context.get()))
.set_in_reply_to(apb::Node::maybe_link(reply.reply_to.get())) .set_in_reply_to(apb::Node::maybe_link(reply.reply_to.get()))
.set_to(apb::Node::links(to_vec)) .set_to(apb::Node::links(to_vec))
@ -299,11 +299,11 @@ pub fn AdvancedPostBox(advanced: WriteSignal<bool>) -> impl IntoView {
if embedded.get() { if embedded.get() {
apb::Node::object( apb::Node::object(
serde_json::Value::Object(serde_json::Map::default()) serde_json::Value::Object(serde_json::Map::default())
.set_id(object_id.as_deref()) .set_id(object_id)
.set_object_type(Some(apb::ObjectType::Note)) .set_object_type(Some(apb::ObjectType::Note))
.set_name(name.as_deref()) .set_name(name)
.set_summary(summary.as_deref()) .set_summary(summary)
.set_content(content.as_deref()) .set_content(content)
.set_in_reply_to(apb::Node::maybe_link(reply)) .set_in_reply_to(apb::Node::maybe_link(reply))
.set_context(apb::Node::maybe_link(context)) .set_context(apb::Node::maybe_link(context))
.set_to(apb::Node::links(to)) .set_to(apb::Node::links(to))

View file

@ -1,7 +1,7 @@
use leptos::*; use leptos::*;
use crate::{prelude::*, FALLBACK_IMAGE_URL}; use crate::{prelude::*, FALLBACK_IMAGE_URL};
use apb::{field::OptionalString, Activity, ActivityMut, Actor, Base, Object, ObjectMut}; use apb::{Activity, ActivityMut, Actor, Base, Object, ObjectMut, Shortcuts};
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref REGEX: regex::Regex = regex::Regex::new(r":\w+:").expect("failed compiling custom emoji regex"); static ref REGEX: regex::Regex = regex::Regex::new(r":\w+:").expect("failed compiling custom emoji regex");
@ -12,7 +12,7 @@ pub fn ActorStrip(object: crate::Object) -> impl IntoView {
let actor_id = object.id().unwrap_or_default().to_string(); let actor_id = object.id().unwrap_or_default().to_string();
let username = object.preferred_username().unwrap_or_default().to_string(); let username = object.preferred_username().unwrap_or_default().to_string();
let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string(); let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string();
let avatar = object.icon().get().map(|x| x.url().id().str().unwrap_or(FALLBACK_IMAGE_URL.into())).unwrap_or(FALLBACK_IMAGE_URL.into()); let avatar = object.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into());
view! { view! {
<a href={Uri::web(U::Actor, &actor_id)} class="clean hover"> <a href={Uri::web(U::Actor, &actor_id)} class="clean hover">
<img src={avatar} class="avatar inline mr-s" onerror={format!("this.onerror=null; this.src='{FALLBACK_IMAGE_URL}';")} /><b>{username}</b><small>@{domain}</small> <img src={avatar} class="avatar inline mr-s" onerror={format!("this.onerror=null; this.src='{FALLBACK_IMAGE_URL}';")} /><b>{username}</b><small>@{domain}</small>
@ -29,7 +29,7 @@ pub fn ActorBanner(object: crate::Object) -> impl IntoView {
serde_json::Value::Object(_) => { serde_json::Value::Object(_) => {
let uid = object.id().unwrap_or_default().to_string(); let uid = object.id().unwrap_or_default().to_string();
let uri = Uri::web(U::Actor, &uid); let uri = Uri::web(U::Actor, &uid);
let avatar_url = object.icon().get().map(|x| x.url().id().str().unwrap_or(FALLBACK_IMAGE_URL.into())).unwrap_or(FALLBACK_IMAGE_URL.into()); let avatar_url = object.icon_url().unwrap_or(FALLBACK_IMAGE_URL.into());
let username = object.preferred_username().unwrap_or_default().to_string(); let username = object.preferred_username().unwrap_or_default().to_string();
let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string(); let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string();
let display_name = object.name().unwrap_or_default().to_string(); let display_name = object.name().unwrap_or_default().to_string();
@ -70,7 +70,7 @@ pub fn FollowRequestButtons(activity_id: String, actor_id: String) -> impl IntoV
// TODO lmao what is going on with this double move / triple clone ??????????? // TODO lmao what is going on with this double move / triple clone ???????????
let _activity_id = activity_id.clone(); let _activity_id = activity_id.clone();
let _actor_id = actor_id.clone(); let _actor_id = actor_id.clone();
let from_actor = cache::OBJECTS.get(&activity_id).map(|x| x.actor().id().str().unwrap_or_default()).unwrap_or_default(); let from_actor = cache::OBJECTS.get(&activity_id).and_then(|x| x.actor().id().ok()).unwrap_or_default();
let _from_actor = from_actor.clone(); let _from_actor = from_actor.clone();
if actor_id == auth.user_id() { if actor_id == auth.user_id() {
Some(view! { Some(view! {

View file

@ -66,7 +66,7 @@ impl FiltersConfig {
let mut reply_filter = true; let mut reply_filter = true;
if let Ok(obj_id) = item.object().id() { if let Ok(obj_id) = item.object().id() {
if let Some(obj) = crate::cache::OBJECTS.get(obj_id) { if let Some(obj) = crate::cache::OBJECTS.get(&obj_id) {
if obj.in_reply_to().id().is_ok() { if obj.in_reply_to().id().is_ok() {
reply_filter = self.replies; reply_filter = self.replies;
} }

View file

@ -2,7 +2,7 @@ use leptos::*;
use crate::{prelude::*, URL_SENSITIVE}; use crate::{prelude::*, URL_SENSITIVE};
use base64::prelude::*; use base64::prelude::*;
use apb::{field::OptionalString, Document, Object}; use apb::{Document, Object};
fn uncloak(txt: Option<&str>) -> Option<String> { fn uncloak(txt: Option<&str>) -> Option<String> {
let decoded = BASE64_URL_SAFE.decode(txt?).ok()?; let decoded = BASE64_URL_SAFE.decode(txt?).ok()?;
@ -17,11 +17,10 @@ pub fn Attachment(
) -> impl IntoView { ) -> impl IntoView {
let config = use_context::<Signal<crate::Config>>().expect("missing config context"); let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let (expand, set_expand) = create_signal(false); let (expand, set_expand) = create_signal(false);
let href = object.url().id().str().unwrap_or_default(); let href = object.url().id().ok().unwrap_or_default();
let uncloaked = uncloak(href.split('/').last()).unwrap_or_default(); let uncloaked = uncloak(href.split('/').last()).unwrap_or_default();
let media_type = object.media_type() let media_type = object.media_type()
.unwrap_or("link") // TODO make it an Option rather than defaulting to link everywhere .unwrap_or("link".to_string()); // TODO make it an Option rather than defaulting to link everywhere
.to_string();
let mut kind = media_type let mut kind = media_type
.split('/') .split('/')
.next() .next()

View file

@ -3,29 +3,26 @@ use std::sync::Arc;
use leptos::*; use leptos::*;
use crate::{prelude::*, URL_SENSITIVE}; use crate::{prelude::*, URL_SENSITIVE};
use apb::{field::OptionalString, target::Addressed, ActivityMut, Base, Collection, CollectionMut, Object, ObjectMut}; use apb::{target::Addressed, ActivityMut, Base, Collection, CollectionMut, Object, ObjectMut};
#[component] #[component]
pub fn Object(object: crate::Object) -> impl IntoView { pub fn Object(object: crate::Object) -> impl IntoView {
let oid = object.id().unwrap_or_default().to_string(); let oid = object.id().unwrap_or_default().to_string();
let author_id = object.attributed_to().id().str().unwrap_or_default(); let author_id = object.attributed_to().id().ok().unwrap_or_default();
let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into()); let author = cache::OBJECTS.get_or(&author_id, serde_json::Value::String(author_id.clone()).into());
let sensitive = object.sensitive().unwrap_or_default(); let sensitive = object.sensitive().unwrap_or_default();
let addressed = object.addressed(); let addressed = object.addressed();
let public = addressed.iter().any(|x| x.as_str() == apb::target::PUBLIC); let public = addressed.iter().any(|x| x.as_str() == apb::target::PUBLIC);
let external_url = object.url().id().str().unwrap_or_else(|| oid.clone()); let external_url = object.url().id().ok().unwrap_or_else(|| oid.clone());
let attachments = object.attachment() let attachments = object.attachment()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| x.extract()) // TODO maybe show links? .filter_map(|x| x.into_inner().ok()) // TODO maybe show links?
.map(|x| view! { <Attachment object=x sensitive=sensitive /> }) .map(|x| view! { <Attachment object=x sensitive=sensitive /> })
.collect_view(); .collect_view();
let comments = object.replies().get() let comments = object.replies().inner().and_then(|x| x.total_items()).unwrap_or_default();
.map_or(0, |x| x.total_items().unwrap_or(0)); let shares = object.shares().inner().and_then(|x| x.total_items()).unwrap_or_default();
let shares = object.shares().get() let likes = object.likes().inner().and_then(|x| x.total_items()).unwrap_or_default();
.map_or(0, |x| x.total_items().unwrap_or(0));
let likes = object.likes().get()
.map_or(0, |x| x.total_items().unwrap_or(0));
let already_liked = object.liked_by_me().unwrap_or(false); let already_liked = object.liked_by_me().unwrap_or(false);
let attachments_padding = if object.attachment().is_empty() { let attachments_padding = if object.attachment().is_empty() {
@ -34,9 +31,9 @@ pub fn Object(object: crate::Object) -> impl IntoView {
Some(view! { <div class="pb-1"></div> }) Some(view! { <div class="pb-1"></div> })
}; };
let content = mdhtml::safe_html(object.content().unwrap_or_default()); let content = mdhtml::safe_html(&object.content().unwrap_or_default());
let audience_badge = object.audience().id().str() let audience_badge = object.audience().id().ok()
.map(|x| { .map(|x| {
// TODO this isn't guaranteed to work every time... // TODO this isn't guaranteed to work every time...
let name = x.split('/').last().unwrap_or_default().to_string(); let name = x.split('/').last().unwrap_or_default().to_string();
@ -56,7 +53,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
.id() .id()
.ok() .ok()
.map(|x| { .map(|x| {
let href = Uri::web(U::Object, x); let href = Uri::web(U::Object, &x);
view! { view! {
<a class="clean dim" href={href}> <a class="clean dim" href={href}>
<span class="border-button ml-s" > <span class="border-button ml-s" >
@ -103,7 +100,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
uid.replace("https://", "").replace("http://", "").split('/').next().unwrap_or_default().to_string(), uid.replace("https://", "").replace("http://", "").split('/').next().unwrap_or_default().to_string(),
) )
}; };
let href = Uri::web(U::Actor, uid); let href = Uri::web(U::Actor, &uid);
Some(view! { Some(view! {
<a class="clean dim" href={href}> <a class="clean dim" href={href}>
<span class="border-button ml-s" title={format!("@{username}@{domain}")} > <span class="border-button ml-s" title={format!("@{username}@{domain}")} >
@ -119,7 +116,7 @@ pub fn Object(object: crate::Object) -> impl IntoView {
} }
}).collect_view(); }).collect_view();
let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| { let post_image = object.image().inner().and_then(|x| x.url().id()).ok().map(|x| {
let (expand, set_expand) = create_signal(false); let (expand, set_expand) = create_signal(false);
view! { view! {
<img <img
@ -180,11 +177,11 @@ pub fn Object(object: crate::Object) -> impl IntoView {
<tr> <tr>
<td><ActorBanner object=author /></td> <td><ActorBanner object=author /></td>
<td class="rev" > <td class="rev" >
{object.in_reply_to().id().str().map(|reply| view! { {object.in_reply_to().id().ok().map(|reply| view! {
<small><i><a class="clean" href={Uri::web(U::Object, &reply)} title={reply}>reply</a></i></small> <small><i><a class="clean" href={Uri::web(U::Object, &reply)} title={reply}>reply</a></i></small>
})} })}
<PrivacyMarker addressed=addressed /> <PrivacyMarker addressed=addressed />
<a class="clean hover ml-s" href={Uri::web(U::Object, object.id().unwrap_or_default())}> <a class="clean hover ml-s" href={Uri::web(U::Object, &object.id().unwrap_or_default())}>
<DateTime t=object.published().ok() /> <DateTime t=object.published().ok() />
</a> </a>
<sup><small><a class="clean ml-s" href={external_url} target="_blank">""</a></small></sup> <sup><small><a class="clean ml-s" href={external_url} target="_blank">""</a></small></sup>
@ -262,7 +259,7 @@ pub fn LikeButton(
set_count.set(count.get() + 1); set_count.set(count.get() + 1);
if let Some(cached) = cache::OBJECTS.get(&target) { if let Some(cached) = cache::OBJECTS.get(&target) {
let mut new = (*cached).clone().set_liked_by_me(Some(true)); let mut new = (*cached).clone().set_liked_by_me(Some(true));
if let Some(likes) = new.likes().get() { if let Ok(likes) = new.likes().inner() {
if let Ok(count) = likes.total_items() { if let Ok(count) = likes.total_items() {
new = new.set_likes(apb::Node::object(likes.clone().set_total_items(Some(count + 1)))); new = new.set_likes(apb::Node::object(likes.clone().set_total_items(Some(count + 1))));
} }

View file

@ -13,7 +13,7 @@ pub fn ObjectView() -> impl IntoView {
move |oid| async move { move |oid| async move {
let obj = cache::OBJECTS.resolve(&oid, U::Object, auth).await?; let obj = cache::OBJECTS.resolve(&oid, U::Object, auth).await?;
if let Ok(author) = obj.attributed_to().id() { if let Ok(author) = obj.attributed_to().id() {
cache::OBJECTS.resolve(author, U::Actor, auth).await; cache::OBJECTS.resolve(&author, U::Actor, auth).await;
} }
Some(obj) Some(obj)
@ -36,8 +36,8 @@ pub fn ObjectView() -> impl IntoView {
Some(Some(o)) => { Some(Some(o)) => {
let object = o.clone(); let object = o.clone();
let oid = o.id().unwrap_or_default(); let oid = o.id().unwrap_or_default();
let base = Uri::web(U::Object, oid); let base = Uri::web(U::Object, &oid);
let api = Uri::api(U::Object, oid, false); let api = Uri::api(U::Object, &oid, false);
view!{ view!{
<Object object=object /> <Object object=object />
<hr class="color ma-2" /> <hr class="color ma-2" />

View file

@ -21,10 +21,15 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
let banner_url_ref: NodeRef<html::Input> = create_node_ref(); let banner_url_ref: NodeRef<html::Input> = create_node_ref();
let myself = cache::OBJECTS.get(&auth.userid.get_untracked().unwrap_or_default()); let myself = cache::OBJECTS.get(&auth.userid.get_untracked().unwrap_or_default());
let curr_display_name = myself.as_ref().and_then(|x| Some(x.name().ok()?.to_string())).unwrap_or_default(); let (curr_display_name, curr_summary, curr_icon, curr_banner) = match myself.as_ref() {
let curr_summary = myself.as_ref().and_then(|x| Some(x.summary().ok()?.to_string())).unwrap_or_default(); None => (String::default(), String::default(), String::default(), String::default()),
let curr_icon = myself.as_ref().and_then(|x| Some(x.icon().get()?.url().id().ok()?.to_string())).unwrap_or_default(); Some(myself) => (
let curr_banner = myself.as_ref().and_then(|x| Some(x.image().get()?.url().id().ok()?.to_string())).unwrap_or_default(); myself.name().unwrap_or_default().to_string(),
myself.summary().unwrap_or_default().to_string(),
myself.icon().inner().and_then(|x| Ok(x.url().id()?.to_string())).unwrap_or_default(),
myself.image().inner().and_then(|x| Ok(x.url().id()?.to_string())).unwrap_or_default(),
),
};
macro_rules! get_cfg { macro_rules! get_cfg {
(filter $field:ident) => { (filter $field:ident) => {
@ -175,8 +180,8 @@ pub fn ConfigPage(setter: WriteSignal<crate::Config>) -> impl IntoView {
.set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string(), format!("{id}/followers")])) .set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string(), format!("{id}/followers")]))
.set_object(apb::Node::object( .set_object(apb::Node::object(
(*me).clone() (*me).clone()
.set_name(display_name.as_deref()) .set_name(display_name)
.set_summary(summary.as_deref()) .set_summary(summary)
.set_icon(apb::Node::maybe_object(avatar)) .set_icon(apb::Node::maybe_object(avatar))
.set_image(apb::Node::maybe_object(banner)) .set_image(apb::Node::maybe_object(banner))
.set_published(Some(chrono::Utc::now())) .set_published(Some(chrono::Utc::now()))

View file

@ -41,7 +41,7 @@ pub fn SearchPage() -> impl IntoView {
.ordered_items() .ordered_items()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| x.extract()) .filter_map(|x| x.into_inner().ok())
.collect(), .collect(),
auth auth
).await ).await

View file

@ -3,7 +3,7 @@ pub mod thread;
use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use std::{collections::BTreeSet, pin::Pin, sync::Arc};
use apb::{field::OptionalString, Activity, ActivityMut, Actor, Base, Object}; use apb::{Activity, ActivityMut, Actor, Base, Object};
use leptos::*; use leptos::*;
use crate::prelude::*; use crate::prelude::*;
@ -89,7 +89,7 @@ impl Timeline {
.ordered_items() .ordered_items()
.flat() .flat()
.into_iter() .into_iter()
.filter_map(|x| x.extract()) .filter_map(|x| x.into_inner().ok())
.collect(); .collect();
let mut feed = self.feed.get_untracked(); let mut feed = self.feed.get_untracked();
@ -121,18 +121,18 @@ pub async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth)
for activity in activities { for activity in activities {
let activity_type = activity.activity_type().unwrap_or(apb::ActivityType::Activity); let activity_type = activity.activity_type().unwrap_or(apb::ActivityType::Activity);
// save embedded object if present // save embedded object if present
if let Some(object) = activity.object().get() { if let Ok(object) = activity.object().inner() {
// also fetch actor attributed to // also fetch actor attributed to
if let Some(attributed_to) = object.attributed_to().id().str() { if let Ok(attributed_to) = object.attributed_to().id() {
actors_seen.insert(attributed_to); actors_seen.insert(attributed_to);
} }
if let Ok(object_uri) = object.id() { if let Ok(object_uri) = object.id() {
cache::OBJECTS.store(object_uri, Arc::new(object.clone())); cache::OBJECTS.store(&object_uri, Arc::new(object.clone()));
} else { } else {
tracing::warn!("embedded object without id: {object:?}"); tracing::warn!("embedded object without id: {object:?}");
} }
} else { // try fetching it } else { // try fetching it
if let Some(object_id) = activity.object().id().str() { if let Ok(object_id) = activity.object().id() {
if !gonna_fetch.contains(&object_id) { if !gonna_fetch.contains(&object_id) {
let fetch_kind = match activity_type { let fetch_kind = match activity_type {
apb::ActivityType::Follow => U::Actor, apb::ActivityType::Follow => U::Actor,
@ -145,25 +145,25 @@ pub async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth)
} }
// save activity, removing embedded object // save activity, removing embedded object
let object_id = activity.object().id().str(); let object_id = activity.object().id().ok();
if let Some(activity_id) = activity.id().str() { if let Ok(activity_id) = activity.id() {
out.push(activity_id.to_string()); out.push(activity_id.to_string());
cache::OBJECTS.store( cache::OBJECTS.store(
&activity_id, &activity_id,
Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id))) Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id)))
); );
} else if let Some(object_id) = activity.object().id().str() { } else if let Ok(object_id) = activity.object().id() {
out.push(object_id); out.push(object_id);
} }
if let Some(uid) = activity.attributed_to().id().str() { if let Ok(uid) = activity.attributed_to().id() {
if cache::OBJECTS.get(&uid).is_none() && !gonna_fetch.contains(&uid) { if cache::OBJECTS.get(&uid).is_none() && !gonna_fetch.contains(&uid) {
gonna_fetch.insert(uid.clone()); gonna_fetch.insert(uid.clone());
sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth))); sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth)));
} }
} }
if let Some(uid) = activity.actor().id().str() { if let Ok(uid) = activity.actor().id() {
if cache::OBJECTS.get(&uid).is_none() && !gonna_fetch.contains(&uid) { if cache::OBJECTS.get(&uid).is_none() && !gonna_fetch.contains(&uid) {
gonna_fetch.insert(uid.clone()); gonna_fetch.insert(uid.clone());
sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth))); sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth)));
@ -185,7 +185,7 @@ async fn fetch_and_update(kind: U, id: String, auth: Auth) {
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")), Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
Ok(data) => { Ok(data) => {
if data.actor_type().is_ok() { if data.actor_type().is_ok() {
if let Some(url) = data.url().id().str() { if let Ok(url) = data.url().id() {
cache::WEBFINGER.store(&id, url); cache::WEBFINGER.store(&id, url);
} }
} }
@ -198,8 +198,8 @@ async fn fetch_and_update_with_user(kind: U, id: String, auth: Auth) {
fetch_and_update(kind, id.clone(), auth).await; fetch_and_update(kind, id.clone(), auth).await;
if let Some(obj) = cache::OBJECTS.get(&id) { if let Some(obj) = cache::OBJECTS.get(&id) {
if let Some(actor_id) = match kind { if let Some(actor_id) = match kind {
U::Object => obj.attributed_to().id().str(), U::Object => obj.attributed_to().id().ok(),
U::Activity => obj.actor().id().str(), U::Activity => obj.actor().id().ok(),
U::Actor => None, U::Actor => None,
U::Hashtag => None, U::Hashtag => None,
} { } {

View file

@ -1,4 +1,4 @@
use apb::{field::OptionalString, Activity, Base, Object}; use apb::{Activity, Base, Object};
use leptos::*; use leptos::*;
use crate::prelude::*; use crate::prelude::*;
use super::Timeline; use super::Timeline;
@ -40,15 +40,15 @@ fn FeedRecursive(tl: Timeline, root: String) -> impl IntoView {
let (oid, reply) = match document.object_type().ok()? { let (oid, reply) = match document.object_type().ok()? {
// if it's a create, get and check created object: does it reply to root? // if it's a create, get and check created object: does it reply to root?
apb::ObjectType::Activity(apb::ActivityType::Create) => { apb::ObjectType::Activity(apb::ActivityType::Create) => {
let object = cache::OBJECTS.get(document.object().id().ok()?)?; let object = cache::OBJECTS.get(&document.object().id().ok()?)?;
(object.id().str()?, object.in_reply_to().id().str()?) (object.id().ok()?, object.in_reply_to().id().ok()?)
}, },
// if it's a raw note, directly check if it replies to root // if it's a raw note, directly check if it replies to root
apb::ObjectType::Note => (document.id().str()?, document.in_reply_to().id().str()?), apb::ObjectType::Note => (document.id().ok()?, document.in_reply_to().id().ok()?),
// if it's anything else, check if it relates to root, maybe like or announce? // if it's anything else, check if it relates to root, maybe like or announce?
_ => (document.id().str()?, document.object().id().str()?), _ => (document.id().ok()?, document.object().id().ok()?),
}; };
if reply == root { if reply == root {
Some((oid, document)) Some((oid, document))