mirror of
https://github.com/astro/buzzrelay.git
synced 2024-11-24 21:11:00 +00:00
parent
c823236a3c
commit
2c625af1f8
3 changed files with 103 additions and 6 deletions
31
src/actor.rs
31
src/actor.rs
|
@ -5,9 +5,11 @@ use sigh::{PublicKey, Key};
|
||||||
use crate::activitypub;
|
use crate::activitypub;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum ActorKind {
|
pub enum ActorKind {
|
||||||
TagRelay(String),
|
TagRelay(String),
|
||||||
InstanceRelay(String),
|
InstanceRelay(String),
|
||||||
|
LanguageRelay(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActorKind {
|
impl ActorKind {
|
||||||
|
@ -17,6 +19,18 @@ impl ActorKind {
|
||||||
.replace(char::is_whitespace, "");
|
.replace(char::is_whitespace, "");
|
||||||
ActorKind::TagRelay(tag)
|
ActorKind::TagRelay(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -32,12 +46,17 @@ impl Actor {
|
||||||
if uri.starts_with("acct:tag-") {
|
if uri.starts_with("acct:tag-") {
|
||||||
let off = "acct:tag-".len();
|
let off = "acct:tag-".len();
|
||||||
let Some(at) = uri.find('@') else { return None; };
|
let Some(at) = uri.find('@') else { return None; };
|
||||||
kind = ActorKind::TagRelay(uri[off..at].to_string());
|
kind = ActorKind::from_tag(&uri[off..at]);
|
||||||
host = Arc::new(uri[at + 1..].to_string());
|
host = Arc::new(uri[at + 1..].to_string());
|
||||||
} else if uri.starts_with("acct:instance-") {
|
} else if uri.starts_with("acct:instance-") {
|
||||||
let off = "acct:instance-".len();
|
let off = "acct:instance-".len();
|
||||||
let Some(at) = uri.find('@') else { return None; };
|
let Some(at) = uri.find('@') else { return None; };
|
||||||
kind = ActorKind::InstanceRelay(uri[off..at].to_string());
|
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])?;
|
||||||
host = Arc::new(uri[at + 1..].to_string());
|
host = Arc::new(uri[at + 1..].to_string());
|
||||||
} else if uri.starts_with("https://") {
|
} else if uri.starts_with("https://") {
|
||||||
uri = &uri[8..];
|
uri = &uri[8..];
|
||||||
|
@ -53,6 +72,8 @@ impl Actor {
|
||||||
ActorKind::TagRelay(topic.to_string()),
|
ActorKind::TagRelay(topic.to_string()),
|
||||||
"instance" =>
|
"instance" =>
|
||||||
ActorKind::InstanceRelay(topic.to_string()),
|
ActorKind::InstanceRelay(topic.to_string()),
|
||||||
|
"language" =>
|
||||||
|
ActorKind::LanguageRelay(topic.to_string()),
|
||||||
_ =>
|
_ =>
|
||||||
return None,
|
return None,
|
||||||
};
|
};
|
||||||
|
@ -69,6 +90,8 @@ impl Actor {
|
||||||
format!("https://{}/tag/{}", self.host, tag),
|
format!("https://{}/tag/{}", self.host, tag),
|
||||||
ActorKind::InstanceRelay(instance) =>
|
ActorKind::InstanceRelay(instance) =>
|
||||||
format!("https://{}/instance/{}", self.host, instance),
|
format!("https://{}/instance/{}", self.host, instance),
|
||||||
|
ActorKind::LanguageRelay(language) =>
|
||||||
|
format!("https://{}/language/{}", self.host, language),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +109,8 @@ impl Actor {
|
||||||
format!("#{}", tag),
|
format!("#{}", tag),
|
||||||
ActorKind::InstanceRelay(instance) =>
|
ActorKind::InstanceRelay(instance) =>
|
||||||
instance.to_string(),
|
instance.to_string(),
|
||||||
|
ActorKind::LanguageRelay(language) =>
|
||||||
|
format!("in {}", language),
|
||||||
}),
|
}),
|
||||||
icon: Some(activitypub::Media {
|
icon: Some(activitypub::Media {
|
||||||
media_type: Some("Image".to_string()),
|
media_type: Some("Image".to_string()),
|
||||||
|
@ -107,6 +132,8 @@ impl Actor {
|
||||||
format!("tag-{}", tag),
|
format!("tag-{}", tag),
|
||||||
ActorKind::InstanceRelay(instance) =>
|
ActorKind::InstanceRelay(instance) =>
|
||||||
format!("instance-{}", instance),
|
format!("instance-{}", instance),
|
||||||
|
ActorKind::LanguageRelay(language) =>
|
||||||
|
format!("language-{}", language),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/main.rs
33
src/main.rs
|
@ -90,6 +90,22 @@ async fn get_instance_actor(
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_language_actor(
|
||||||
|
axum::extract::State(state): axum::extract::State<State>,
|
||||||
|
Path(language): Path<String>
|
||||||
|
) -> Response {
|
||||||
|
track_request("GET", "actor", "language");
|
||||||
|
let Some(kind) = actor::ActorKind::from_language(&language) else {
|
||||||
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
|
};
|
||||||
|
let target = actor::Actor {
|
||||||
|
host: state.hostname.clone(),
|
||||||
|
kind,
|
||||||
|
};
|
||||||
|
target.as_activitypub(&state.pub_key)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
async fn post_tag_relay(
|
async fn post_tag_relay(
|
||||||
axum::extract::State(state): axum::extract::State<State>,
|
axum::extract::State(state): axum::extract::State<State>,
|
||||||
Path(tag): Path<String>,
|
Path(tag): Path<String>,
|
||||||
|
@ -114,6 +130,21 @@ async fn post_instance_relay(
|
||||||
post_relay(state, endpoint, target).await
|
post_relay(state, endpoint, target).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn post_language_relay(
|
||||||
|
axum::extract::State(state): axum::extract::State<State>,
|
||||||
|
Path(language): Path<String>,
|
||||||
|
endpoint: endpoint::Endpoint<'_>
|
||||||
|
) -> Response {
|
||||||
|
let Some(kind) = actor::ActorKind::from_language(&language) else {
|
||||||
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
|
};
|
||||||
|
let target = actor::Actor {
|
||||||
|
host: state.hostname.clone(),
|
||||||
|
kind,
|
||||||
|
};
|
||||||
|
post_relay(state, endpoint, target).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn post_relay(
|
async fn post_relay(
|
||||||
state: State,
|
state: State,
|
||||||
endpoint: endpoint::Endpoint<'_>,
|
endpoint: endpoint::Endpoint<'_>,
|
||||||
|
@ -362,8 +393,10 @@ async fn main() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/tag/:tag", get(get_tag_actor).post(post_tag_relay))
|
.route("/tag/:tag", get(get_tag_actor).post(post_tag_relay))
|
||||||
.route("/instance/:instance", get(get_instance_actor).post(post_instance_relay))
|
.route("/instance/:instance", get(get_instance_actor).post(post_instance_relay))
|
||||||
|
.route("/language/:language", get(get_language_actor).post(post_language_relay))
|
||||||
.route("/tag/:tag/outbox", get(outbox))
|
.route("/tag/:tag/outbox", get(outbox))
|
||||||
.route("/instance/:instance/outbox", get(outbox))
|
.route("/instance/:instance/outbox", get(outbox))
|
||||||
|
.route("/language/:language/outbox", get(outbox))
|
||||||
.route("/.well-known/webfinger", get(webfinger))
|
.route("/.well-known/webfinger", get(webfinger))
|
||||||
.route("/.well-known/nodeinfo", get(nodeinfo))
|
.route("/.well-known/nodeinfo", get(nodeinfo))
|
||||||
.route("/api/v1/instance", get(instanceinfo))
|
.route("/api/v1/instance", get(instanceinfo))
|
||||||
|
|
45
src/relay.rs
45
src/relay.rs
|
@ -4,9 +4,7 @@ use metrics::{increment_counter, histogram};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sigh::PrivateKey;
|
use sigh::PrivateKey;
|
||||||
use tokio::{
|
use tokio::sync::mpsc::Receiver;
|
||||||
sync::mpsc::Receiver,
|
|
||||||
};
|
|
||||||
use crate::{send, actor, state::State};
|
use crate::{send, actor, state::State};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -14,6 +12,7 @@ struct Post<'a> {
|
||||||
pub url: Option<&'a str>,
|
pub url: Option<&'a str>,
|
||||||
pub uri: &'a str,
|
pub uri: &'a str,
|
||||||
pub tags: Option<Vec<Tag<'a>>>,
|
pub tags: Option<Vec<Tag<'a>>>,
|
||||||
|
pub language: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Post<'_> {
|
impl Post<'_> {
|
||||||
|
@ -75,6 +74,10 @@ impl Post<'_> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.chain(
|
||||||
|
self.language
|
||||||
|
.and_then(actor::ActorKind::from_language)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relay_targets(&self, hostname: Arc<String>) -> impl Iterator<Item = actor::Actor> {
|
pub fn relay_targets(&self, hostname: Arc<String>) -> impl Iterator<Item = actor::Actor> {
|
||||||
|
@ -247,10 +250,12 @@ mod test {
|
||||||
tags: Some(vec![Tag {
|
tags: Some(vec![Tag {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
}]),
|
}]),
|
||||||
|
language: Some("en"),
|
||||||
};
|
};
|
||||||
let mut kinds = post.relay_target_kinds();
|
let mut kinds = post.relay_target_kinds();
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::TagRelay("foo".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::TagRelay("foo".to_string())));
|
||||||
|
assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("en".to_string())));
|
||||||
assert_eq!(kinds.next(), None);
|
assert_eq!(kinds.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +267,7 @@ mod test {
|
||||||
tags: Some(vec![Tag {
|
tags: Some(vec![Tag {
|
||||||
name: "",
|
name: "",
|
||||||
}]),
|
}]),
|
||||||
|
language: None,
|
||||||
};
|
};
|
||||||
let mut kinds = post.relay_target_kinds();
|
let mut kinds = post.relay_target_kinds();
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
|
@ -276,6 +282,7 @@ mod test {
|
||||||
tags: Some(vec![Tag {
|
tags: Some(vec![Tag {
|
||||||
name: "23",
|
name: "23",
|
||||||
}]),
|
}]),
|
||||||
|
language: None,
|
||||||
};
|
};
|
||||||
let mut kinds = post.relay_target_kinds();
|
let mut kinds = post.relay_target_kinds();
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
|
@ -291,6 +298,7 @@ mod test {
|
||||||
tags: Some(vec![Tag {
|
tags: Some(vec![Tag {
|
||||||
name: "dd1302",
|
name: "dd1302",
|
||||||
}]),
|
}]),
|
||||||
|
language: None,
|
||||||
};
|
};
|
||||||
let mut kinds = post.relay_target_kinds();
|
let mut kinds = post.relay_target_kinds();
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
|
@ -300,17 +308,46 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn post_relay_kind_jp() {
|
fn post_relay_kind_ja() {
|
||||||
let post = Post {
|
let post = Post {
|
||||||
url: Some("http://example.com/post/1"),
|
url: Some("http://example.com/post/1"),
|
||||||
uri: "http://example.com/post/1",
|
uri: "http://example.com/post/1",
|
||||||
tags: Some(vec![Tag {
|
tags: Some(vec![Tag {
|
||||||
name: "スコティッシュ・フォールド・ロングヘアー",
|
name: "スコティッシュ・フォールド・ロングヘアー",
|
||||||
}]),
|
}]),
|
||||||
|
language: Some("ja"),
|
||||||
};
|
};
|
||||||
let mut kinds = post.relay_target_kinds();
|
let mut kinds = post.relay_target_kinds();
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
assert_eq!(kinds.next(), Some(ActorKind::TagRelay("sukoteitusiyuhuorudoronguhea".to_string())));
|
assert_eq!(kinds.next(), Some(ActorKind::TagRelay("sukoteitusiyuhuorudoronguhea".to_string())));
|
||||||
|
assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("ja".to_string())));
|
||||||
|
assert_eq!(kinds.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn post_relay_language_long() {
|
||||||
|
let post = Post {
|
||||||
|
url: Some("http://example.com/post/1"),
|
||||||
|
uri: "http://example.com/post/1",
|
||||||
|
tags: None,
|
||||||
|
language: Some("de_CH"),
|
||||||
|
};
|
||||||
|
let mut kinds = post.relay_target_kinds();
|
||||||
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
|
assert_eq!(kinds.next(), Some(ActorKind::LanguageRelay("de".to_string())));
|
||||||
|
assert_eq!(kinds.next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn post_relay_language_invalid() {
|
||||||
|
let post = Post {
|
||||||
|
url: Some("http://example.com/post/1"),
|
||||||
|
uri: "http://example.com/post/1",
|
||||||
|
tags: None,
|
||||||
|
language: Some("23q"),
|
||||||
|
};
|
||||||
|
let mut kinds = post.relay_target_kinds();
|
||||||
|
assert_eq!(kinds.next(), Some(ActorKind::InstanceRelay("example.com".to_string())));
|
||||||
assert_eq!(kinds.next(), None);
|
assert_eq!(kinds.next(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue