2022-12-19 20:20:13 +00:00
|
|
|
use std::sync::Arc;
|
2023-03-03 01:19:48 +00:00
|
|
|
use deunicode::deunicode;
|
2024-04-25 00:14:21 +00:00
|
|
|
use serde_json::json;
|
2022-12-19 20:20:13 +00:00
|
|
|
use sigh::{PublicKey, Key};
|
|
|
|
|
|
|
|
use crate::activitypub;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
2023-11-26 18:56:46 +00:00
|
|
|
#[allow(clippy::enum_variant_names)]
|
2022-12-19 20:20:13 +00:00
|
|
|
pub enum ActorKind {
|
|
|
|
TagRelay(String),
|
|
|
|
InstanceRelay(String),
|
2023-11-26 18:56:46 +00:00
|
|
|
LanguageRelay(String),
|
2022-12-19 20:20:13 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 01:19:48 +00:00
|
|
|
impl ActorKind {
|
|
|
|
pub fn from_tag(tag: &str) -> Self {
|
|
|
|
let tag = deunicode(tag)
|
|
|
|
.to_lowercase()
|
|
|
|
.replace(char::is_whitespace, "");
|
|
|
|
ActorKind::TagRelay(tag)
|
|
|
|
}
|
2023-11-26 18:56:46 +00:00
|
|
|
|
|
|
|
pub fn from_language(language: &str) -> Option<Self> {
|
|
|
|
let language = language.to_lowercase()
|
|
|
|
.chars()
|
|
|
|
.take_while(|c| c.is_alphabetic())
|
|
|
|
.collect::<String>();
|
|
|
|
if language.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(ActorKind::LanguageRelay(language))
|
|
|
|
}
|
|
|
|
}
|
2023-03-03 01:19:48 +00:00
|
|
|
}
|
|
|
|
|
2022-12-19 20:20:13 +00:00
|
|
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub struct Actor {
|
|
|
|
pub host: Arc<String>,
|
|
|
|
pub kind: ActorKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Actor {
|
2023-10-31 03:02:31 +00:00
|
|
|
pub fn from_uri(mut uri: &str) -> Option<Self> {
|
2023-10-31 03:12:03 +00:00
|
|
|
let kind;
|
|
|
|
let host;
|
|
|
|
if uri.starts_with("acct:tag-") {
|
|
|
|
let off = "acct:tag-".len();
|
|
|
|
let Some(at) = uri.find('@') else { return None; };
|
2023-11-26 18:56:46 +00:00
|
|
|
kind = ActorKind::from_tag(&uri[off..at]);
|
2023-10-31 03:12:03 +00:00
|
|
|
host = Arc::new(uri[at + 1..].to_string());
|
|
|
|
} else if uri.starts_with("acct:instance-") {
|
|
|
|
let off = "acct:instance-".len();
|
|
|
|
let Some(at) = uri.find('@') else { return None; };
|
2023-11-26 18:56:46 +00:00
|
|
|
kind = ActorKind::InstanceRelay(uri[off..at].to_lowercase());
|
|
|
|
host = Arc::new(uri[at + 1..].to_string());
|
|
|
|
} else if uri.starts_with("acct:language-") {
|
|
|
|
let off = "acct:language-".len();
|
|
|
|
let Some(at) = uri.find('@') else { return None; };
|
|
|
|
kind = ActorKind::from_language(&uri[off..at])?;
|
2023-10-31 03:12:03 +00:00
|
|
|
host = Arc::new(uri[at + 1..].to_string());
|
|
|
|
} else if uri.starts_with("https://") {
|
|
|
|
uri = &uri[8..];
|
2023-10-31 03:02:31 +00:00
|
|
|
|
2023-10-31 03:12:03 +00:00
|
|
|
let parts = uri.split('/').collect::<Vec<_>>();
|
|
|
|
if parts.len() != 3 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let Ok(topic) = urlencoding::decode(parts[2]) else { return None; };
|
|
|
|
kind = match parts[1] {
|
|
|
|
"tag" =>
|
|
|
|
ActorKind::TagRelay(topic.to_string()),
|
|
|
|
"instance" =>
|
|
|
|
ActorKind::InstanceRelay(topic.to_string()),
|
2023-11-26 18:56:46 +00:00
|
|
|
"language" =>
|
|
|
|
ActorKind::LanguageRelay(topic.to_string()),
|
2023-10-31 03:12:03 +00:00
|
|
|
_ =>
|
|
|
|
return None,
|
|
|
|
};
|
|
|
|
host = Arc::new(parts[0].to_string());
|
|
|
|
} else {
|
2023-10-31 03:02:31 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(Actor { host, kind })
|
|
|
|
}
|
|
|
|
|
2024-02-22 22:10:05 +00:00
|
|
|
pub fn from_object(object: &serde_json::Value) -> Option<Self> {
|
|
|
|
let mut target: Option<String> = None;
|
|
|
|
if let Some(object) = object.as_str() {
|
|
|
|
target = Some(object.to_string());
|
2024-02-21 20:21:33 +00:00
|
|
|
}
|
2024-02-22 22:10:05 +00:00
|
|
|
if let Some(object_0) = object.as_array()
|
|
|
|
.and_then(|object| {
|
|
|
|
if object.len() == 1 {
|
|
|
|
object.first()
|
2024-02-21 20:21:33 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2024-02-22 22:10:05 +00:00
|
|
|
}).and_then(|object_0| object_0.as_str())
|
2024-02-21 20:21:33 +00:00
|
|
|
{
|
2024-02-22 22:10:05 +00:00
|
|
|
target = Some(object_0.to_string());
|
2024-02-21 20:21:33 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 22:10:05 +00:00
|
|
|
target.and_then(|target| Self::from_uri(&target))
|
2024-02-21 20:21:33 +00:00
|
|
|
}
|
|
|
|
|
2022-12-19 20:20:13 +00:00
|
|
|
pub fn uri(&self) -> String {
|
|
|
|
match &self.kind {
|
|
|
|
ActorKind::TagRelay(tag) =>
|
|
|
|
format!("https://{}/tag/{}", self.host, tag),
|
|
|
|
ActorKind::InstanceRelay(instance) =>
|
|
|
|
format!("https://{}/instance/{}", self.host, instance),
|
2023-11-26 18:56:46 +00:00
|
|
|
ActorKind::LanguageRelay(language) =>
|
|
|
|
format!("https://{}/language/{}", self.host, language),
|
2022-12-19 20:20:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn key_id(&self) -> String {
|
|
|
|
format!("{}#key", self.uri())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn as_activitypub(&self, pub_key: &PublicKey) -> activitypub::Actor {
|
|
|
|
activitypub::Actor {
|
2024-04-25 00:14:21 +00:00
|
|
|
jsonld_context: json!([
|
|
|
|
"https://www.w3.org/ns/activitystreams",
|
|
|
|
"https://w3id.org/security/v1"
|
|
|
|
]),
|
2022-12-19 20:20:13 +00:00
|
|
|
actor_type: "Service".to_string(),
|
|
|
|
id: self.uri(),
|
2022-12-23 17:56:45 +00:00
|
|
|
name: Some(match &self.kind {
|
|
|
|
ActorKind::TagRelay(tag) =>
|
2024-03-27 20:39:40 +00:00
|
|
|
format!("#{tag}"),
|
2022-12-23 17:56:45 +00:00
|
|
|
ActorKind::InstanceRelay(instance) =>
|
|
|
|
instance.to_string(),
|
2023-11-26 18:56:46 +00:00
|
|
|
ActorKind::LanguageRelay(language) =>
|
2024-03-27 20:39:40 +00:00
|
|
|
format!("in {language}"),
|
2022-12-23 17:56:45 +00:00
|
|
|
}),
|
|
|
|
icon: Some(activitypub::Media {
|
2023-10-29 23:29:11 +00:00
|
|
|
media_type: Some("Image".to_string()),
|
|
|
|
content_type: Some("image/jpeg".to_string()),
|
2022-12-23 17:56:45 +00:00
|
|
|
url: "https://fedi.buzz/assets/favicon48.png".to_string(),
|
|
|
|
}),
|
2022-12-19 20:20:13 +00:00
|
|
|
inbox: self.uri(),
|
2023-10-30 00:21:30 +00:00
|
|
|
endpoints: Some(activitypub::ActorEndpoints {
|
|
|
|
shared_inbox: format!("https://{}/instance/{}", self.host, self.host),
|
|
|
|
}),
|
2023-10-29 23:29:11 +00:00
|
|
|
outbox: Some(format!("{}/outbox", self.uri())),
|
2022-12-19 20:20:13 +00:00
|
|
|
public_key: activitypub::ActorPublicKey {
|
|
|
|
id: self.key_id(),
|
|
|
|
owner: Some(self.uri()),
|
|
|
|
pem: pub_key.to_pem().unwrap(),
|
|
|
|
},
|
|
|
|
preferred_username: Some(match &self.kind {
|
|
|
|
ActorKind::TagRelay(tag) =>
|
2024-03-27 20:39:40 +00:00
|
|
|
format!("tag-{tag}"),
|
2022-12-19 20:20:13 +00:00
|
|
|
ActorKind::InstanceRelay(instance) =>
|
2024-03-27 20:39:40 +00:00
|
|
|
format!("instance-{instance}"),
|
2023-11-26 18:56:46 +00:00
|
|
|
ActorKind::LanguageRelay(language) =>
|
2024-03-27 20:39:40 +00:00
|
|
|
format!("language-{language}"),
|
2022-12-19 20:20:13 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|