1
Fork 0

WIP: Create apub_inbox crate #8

Draft
steffo wants to merge 40 commits from feature/apub-inbox into main
192 changed files with 2986 additions and 25 deletions

View file

@ -11,7 +11,10 @@
<sourceFolder url="file://$MODULE_DIR$/acrate_rd/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_mime/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_rdserver/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_apub_inbox/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_database/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_astreams/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_astreams/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_utils/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Check" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
<configuration default="false" name="Check" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Validate" nameIsGenerated="true">
<option name="buildProfileId" value="dev" />
<option name="command" value="check" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration default="false" name="Clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Validate">
<option name="buildProfileId" value="dev" />
<option name="command" value="clippy" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration default="false" name="Test" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Test">
<option name="buildProfileId" value="dev" />
<option name="command" value="test" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test ignored" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration default="false" name="Test ignored" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Test">
<option name="buildProfileId" value="dev" />
<option name="command" value="test -- --ignored" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />

View file

@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_utils"]
members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_apub_inbox", "acrate_astreams", "acrate_utils"]

View file

@ -27,6 +27,7 @@ Federation database
### Binaries
- `acrate_rdserver`: Resource descriptor web server
- `acrate_apub_inbox`: ActivityPub inbox web server
### Extra

View file

@ -0,0 +1,28 @@
[package]
name = "acrate_apub_inbox"
version = "0.3.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021"
description = "ActivityPub inbox web server for the acrate project"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2"
keywords = ["activitypub", "apub", "federation"]
categories = ["web-programming"]
[dependencies]
acrate_database = { path = "../acrate_database", features = ["connect"] }
acrate_utils = { path = "../acrate_utils" }
anyhow = "1.0.93"
axum = { version = "0.7.7", features = ["macros"] }
log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] }
micronfig = "1.0.0"
minijinja = "2.5.0"
pretty_env_logger = "0.5.0"
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.132"
tokio = { version = "1.41.1", features = ["macros", "net", "rt-multi-thread"] }
mediatype = { version = "0.19.18", features = ["serde"] }
[lints.clippy]
tabs-in-doc-comments = "allow"
let-and-return = "allow"

View file

@ -0,0 +1,3 @@
micronfig::config!(
ACRATE_APUB_INBOX_BIND_ADDRESS: String > std::net::SocketAddr,
);

View file

@ -0,0 +1,18 @@
use axum::routing::{get, post};
use acrate_utils::web_server;
mod config;
mod route;
#[tokio::main]
async fn main() {
web_server!(
on: *config::ACRATE_APUB_INBOX_BIND_ADDRESS(),
templates: [ ],
routes: {
"/inbox" => post(route::inbox_handler),
"/.healthcheck" => get(route::healthcheck_handler)
}
);
}

View file

@ -0,0 +1,24 @@
use axum::http::{Response, StatusCode};
use acrate_database::connect::connect_async;
pub async fn healthcheck_handler() -> Result<StatusCode, StatusCode> {
log::debug!("Handling an healthcheck request!");
log::trace!("Making sure the database is up...");
let _conn = connect_async()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
log::trace!("Healthcheck successful! Everything's fine!");
Ok(StatusCode::NO_CONTENT)
}
#[allow(unused_mut)] // TODO
pub async fn inbox_handler() -> Result<Response<String>, StatusCode> {
log::debug!("Handling an inbox request!");
log::trace!("Creating a blank response...");
let mut response = Response::new("".to_string());
Ok(response)
}

View file

@ -0,0 +1,30 @@
[package]
name = "acrate_astreams"
version = "0.3.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021"
description = "ActivityStreams definitions and utilities"
repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate"
license = "EUPL-1.2"
keywords = ["activitypub", "activitystreams", "federation", "apub", "astreams"]
categories = ["web-programming"]
[dependencies]
iref = "3.2.2"
json-ld = { version = "0.21.1", features = ["serde", "reqwest"] }
log = "0.4.22"
anyhow = "1.0.95"
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
static-iref = "3.0.0"
thiserror = "2.0.3"
mediatype = { version = "0.19.18", features = ["serde"] }
language-tags = { version = "0.3.2", features = ["serde"] }
[dev-dependencies]
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
tokio-test = "0.4.4"
[lints.clippy]
tabs-in-doc-comments = "allow"
let-and-return = "allow"

View file

@ -0,0 +1,292 @@
use anyhow::{anyhow, Error};
use static_iref::iri;
use anyhow::Result as AResult;
use iref::Iri;
use json_ld::Direction;
use json_ld::object::Any;
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
use crate::activitystreams::{StreamsEntity, StreamsLink};
pub type LangTriple = (String, Option<LangTagBuf>, Option<Direction>);
pub trait StreamsJsonLD<Entity> {
fn jsonld_any_value_string(&self, id: &Iri) -> Option<AResult<String>>;
fn jsonld_iter_value_string(&self, id: &Iri) -> impl Iterator<Item = AResult<String>>;
fn jsonld_iter_value_langstring(&self, id: &Iri) -> impl Iterator<Item = AResult<LangTriple>>;
fn jsonld_any_value_mediatype(&self, id: &Iri) -> Option<AResult<MediaType>>;
fn jsonld_any_node_string(&self, id: &Iri) -> Option<AResult<String>>;
fn jsonld_any_value_langtag(&self, id: &Iri) -> Option<AResult<LangTagBuf>>;
fn jsonld_any_value_u32(&self, id: &Iri) -> Option<AResult<u32>>;
fn jsonld_any_value_u64(&self, id: &Iri) -> Option<AResult<u64>>;
fn jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<Entity>>;
}
impl StreamsJsonLD<json_ld::Node> for json_ld::Node {
fn jsonld_any_value_string(&self, id: &Iri) -> Option<AResult<String>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
Some(Ok(string))
}
fn jsonld_iter_value_string(&self, id: &Iri) -> impl Iterator<Item = AResult<String>> {
let properties = self.properties.get(&id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let strs = values.flat_map(|v| v
.map(|v| v
.as_str()
.ok_or(anyhow!("Couldn't process property as JSON-LD string"))
)
);
let strings = strs.map(|v| v
.map(|v| v
.to_string()
)
);
strings
}
fn jsonld_iter_value_langstring(&self, id: &Iri) -> impl Iterator<Item = AResult<LangTriple>> {
let properties = self.properties.get(&id);
let values = properties.map(|v| v
.as_value()
.ok_or(anyhow!("Couldn't process property as JSON-LD value"))
);
let values = values.flat_map(|v| v
.map(|v| {
let string = v
.as_str()
.ok_or(anyhow!("Expected property to be a langString, but no string was obtained"))?
.to_string();
let lang = v
.language()
.map(|l| l
.as_well_formed()
.ok_or(anyhow!("Expected property to have a valid language tag, but got an invalid one instead"))
);
let lang = match lang {
None => None,
Some(Ok(lang)) => Some(lang.to_owned()),
Some(Err(err)) => return Err(err),
};
let direction = v.direction();
Ok((string, lang, direction))
})
);
values
}
fn jsonld_any_value_mediatype(&self, id: &Iri) -> Option<AResult<MediaType>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let mediatype = match MediaType::parse(r#str) {
Err(e) => return Some(Err(Error::from(e).context("Couldn't parse property as MIME media type"))),
Ok(mediatype) => mediatype,
};
Some(Ok(mediatype))
}
fn jsonld_any_node_string(&self, id: &Iri) -> Option<AResult<String>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let node = match property.as_node() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD node"))),
Some(value) => value,
};
let id = match node.id() {
None => return Some(Err(anyhow!("Couldn't process property's JSON-LD node @id"))),
Some(id) => id
};
let string = id.to_string();
Some(Ok(string))
}
fn jsonld_any_value_langtag(&self, id: &Iri) -> Option<AResult<LangTagBuf>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let r#str = match value.as_str() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD string"))),
Some(r#str) => r#str
};
let string = r#str.to_string();
let langtag = match LangTagBuf::new(string) {
Err(e) => return Some(Err(anyhow!("Couldn't process property as a BCP47 language tag: {e:#?}"))),
Ok(langtag) => langtag,
};
Some(Ok(langtag))
}
fn jsonld_any_value_u32(&self, id: &Iri) -> Option<AResult<u32>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u32 = match number.as_u32() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u32) => r#u32,
};
Some(Ok(r#u32))
}
fn jsonld_any_value_u64(&self, id: &Iri) -> Option<AResult<u64>> {
let property = match self.properties.get_any(&id) {
None => return None,
Some(property) => property,
};
let value = match property.as_value() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD value"))),
Some(value) => value,
};
let number = match value.as_number() {
None => return Some(Err(anyhow!("Couldn't process property as JSON-LD number"))),
Some(number) => number
};
let r#u64 = match number.as_u64() {
None => return Some(Err(anyhow!("Couldn't losslessly convert JSON-LD number to u64"))),
Some(r#u64) => r#u64,
};
Some(Ok(r#u64))
}
fn jsonld_iter_node_entity(&self, id: &Iri) -> impl Iterator<Item = AResult<json_ld::Node>> {
let properties = self.properties.get(&id);
let nodes = properties.map(|v| v
.as_node()
.ok_or(anyhow!("Couldn't process property as JSON-LD node"))
.map(|v| v
.clone()
)
);
nodes
}
}
impl StreamsEntity<json_ld::Node> for json_ld::Node {
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<json_ld::Node>> {
self.jsonld_iter_node_entity(
iri!("https://www.w3.org/ns/activitystreams#preview")
)
}
}
impl StreamsLink<json_ld::Node> for json_ld::Node {
fn activitystreams_href(&self) -> Option<AResult<String>> {
self.jsonld_any_node_string(
iri!("https://www.w3.org/ns/activitystreams#href")
)
}
fn activitystreams_rels_lenient(&self) -> impl Iterator<Item = AResult<String>> {
self.jsonld_iter_value_string(
iri!("https://www.w3.org/ns/activitystreams#rel")
)
}
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>> {
self.jsonld_any_value_mediatype(
iri!("https://www.w3.org/ns/activitystreams#mediaType")
)
}
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<LangTriple>> {
self.jsonld_iter_value_langstring(
iri!("https://www.w3.org/ns/activitystreams#name")
)
}
fn activitystreams_hreflang(&self) -> Option<AResult<LangTagBuf>> {
self.jsonld_any_value_langtag(
iri!("https://www.w3.org/ns/activitystreams#hreflang")
)
}
fn activitystreams_height(&self) -> Option<AResult<u64>> {
self.jsonld_any_value_u64(
iri!("https://www.w3.org/ns/activitystreams#height")
)
}
fn activitystreams_width(&self) -> Option<AResult<u64>> {
self.jsonld_any_value_u64(
iri!("https://www.w3.org/ns/activitystreams#width")
)
}
}

View file

@ -0,0 +1,54 @@
//! Struct definitions for ActivityStreams Core and Extended Types.
//!
//! # Functional
//!
//! > Properties marked as being "Functional" can have only one value.
//! >
//! > Items not marked as "Functional" can have multiple values.
//!
//! # Specification
//!
//! - <https://www.w3.org/TR/activitystreams-vocabulary/>
//!
use anyhow::Result as AResult;
use json_ld::Direction;
use json_ld::syntax::LangTagBuf;
use mediatype::MediaType;
pub mod jsonld;
/// Something that is either a [`StreamsObject`] or a [`StreamsLink`].
pub trait StreamsEntity<Preview> {
fn activitystreams_previews(&self) -> impl Iterator<Item = AResult<Preview>>;
}
/// Something that can be considered a `https://www.w3.org/ns/activitystreams#Object`.
pub trait StreamsObject<Preview> where
Self: StreamsEntity<Preview>,
{
}
/// Something that can be considered a `https://www.w3.org/ns/activitystreams#Link`.
pub trait StreamsLink<Preview> where
Self: StreamsEntity<Preview>,
Preview: StreamsEntity<Preview>,
{
fn activitystreams_href(&self) -> Option<AResult<String>>;
// FIXME: This accepts any kind of string, and does not filter to HTML link relations
fn activitystreams_rels_lenient(&self) -> impl Iterator<Item = AResult<String>>;
fn activitystreams_mediatype(&self) -> Option<AResult<MediaType>>;
fn activitystreams_names(&self) -> impl Iterator<Item = AResult<(String, Option<LangTagBuf>, Option<Direction>)>>;
fn activitystreams_hreflang(&self) -> Option<AResult<LangTagBuf>>;
// FIXME: This doesn't accept numbers greater than u64
fn activitystreams_height(&self) -> Option<AResult<u64>>;
// FIXME: This doesn't accept numbers greater than u64
fn activitystreams_width(&self) -> Option<AResult<u64>>;
}

View file

@ -0,0 +1,6 @@
//! Struct definitions for Lemmy's Extension Types.
//!
//! # Specification
//!
//! - <https://join-lemmy.org/docs/contributors/05-federation.html>
//!

View file

@ -0,0 +1,6 @@
pub mod activitystreams;
pub mod mastodon;
pub mod miajetzt;
pub mod litepub;
pub mod lemmy;

View file

@ -0,0 +1,6 @@
//! Struct definitions for Pleroma and Akkoma's Extension Types.
//!
//! # Specification
//!
//! - <https://docs.akkoma.dev/develop/development/ap_extensions/>
//!

View file

@ -0,0 +1,6 @@
//! Struct definitions for Mastodon's Extension Types.
//!
//! # Specification
//!
//! - <https://docs.joinmastodon.org/spec/activitypub/#contexts>
//!

View file

@ -0,0 +1,6 @@
//! Struct definitions for mia's Extension Types.
//!
//! # Specification
//!
//! - <https://ns.mia.jetzt/as/>
//!

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Object",
"id": "http://www.test.example/object/1",
"name": "A Simple, non-specific object"
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally accepted Joe into the club",
"type": "Accept",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Person",
"name": "Joe"
},
"target": {
"type": "Group",
"name": "The Club"
}
}

View file

@ -0,0 +1,11 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Page 1 of Sally's blog posts",
"type": "CollectionPage",
"prev": "http://example.org/collection?page=1",
"items": [
"http://example.org/posts/1",
"http://example.org/posts/2",
"http://example.org/posts/3"
]
}

View file

@ -0,0 +1,16 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Page 1 of Sally's blog posts",
"type": "CollectionPage",
"prev": {
"type": "Link",
"name": "Previous Page",
"href": "http://example.org/collection?page=1"
},
"items": [
"http://example.org/posts/1",
"http://example.org/posts/2",
"http://example.org/posts/3"
]
}

View file

@ -0,0 +1,16 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Video",
"name": "Cool New Movie",
"duration": "PT2H30M",
"preview": {
"type": "Video",
"name": "Trailer",
"duration": "PT1M",
"url": {
"href": "http://example.org/trailer.mkv",
"mediaType": "video/mkv"
}
}
}

View file

@ -0,0 +1,11 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally checked that her flight was on time",
"type": ["Activity", "http://www.verbs.example/Check"],
"actor": "http://sally.example.org",
"object": "http://example.org/flights/1",
"result": {
"type": "http://www.types.example/flightstatus",
"name": "On Time"
}
}

View file

@ -0,0 +1,19 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "A simple note",
"type": "Note",
"id": "http://www.test.example/notes/1",
"content": "I am fine.",
"replies": {
"type": "Collection",
"totalItems": 1,
"items": [
{
"summary": "A response to the note",
"type": "Note",
"content": "I am glad to hear it.",
"inReplyTo": "http://www.test.example/notes/1"
}
]
}
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Image",
"summary": "Picture of Sally",
"url": "http://example.org/sally.jpg",
"tag": [
{
"type": "Person",
"id": "http://sally.example.org",
"name": "Sally"
}
]
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally offered the post to John",
"type": "Offer",
"actor": "http://sally.example.org",
"object": "http://example.org/posts/1",
"target": "http://john.example.org"
}

View file

@ -0,0 +1,12 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally offered the post to John",
"type": "Offer",
"actor": "http://sally.example.org",
"object": "http://example.org/posts/1",
"target": {
"type": "Person",
"name": "John"
}
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally offered the post to John",
"type": "Offer",
"actor": "http://sally.example.org",
"object": "http://example.org/posts/1",
"target": "http://john.example.org",
"to": [ "http://joe.example.org" ]
}

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"name": "4Q Sales Forecast",
"url": "http://example.org/4q-sales-forecast.pdf"
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally tentatively accepted an invitation to a party",
"type": "TentativeAccept",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Invite",
"actor": "http://john.example.org",
"object": {
"type": "Event",
"name": "Going-Away Party for Jim"
}
}
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"name": "4Q Sales Forecast",
"url": {
"type": "Link",
"href": "http://example.org/4q-sales-forecast.pdf"
}
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"name": "4Q Sales Forecast",
"url": [
{
"type": "Link",
"href": "http://example.org/4q-sales-forecast.pdf",
"mediaType": "application/pdf"
},
{
"type": "Link",
"href": "http://example.org/4q-sales-forecast.html",
"mediaType": "text/html"
}
]
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "Liu Gu Lu Cun, Pingdu, Qingdao, Shandong, China",
"type": "Place",
"latitude": 36.75,
"longitude": 119.7667,
"accuracy": 94.5
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "Fresno Area",
"altitude": 15.0,
"latitude": 36.75,
"longitude": 119.7667,
"units": "miles"
}

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "A simple note",
"type": "Note",
"content": "A <em>simple</em> note"
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "A simple note",
"type": "Note",
"contentMap": {
"en": "A <em>simple</em> note",
"es": "Una nota <em>sencilla</em>",
"zh-Hans": "一段<em>简单的</em>笔记"
}
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "A simple note",
"type": "Note",
"mediaType": "text/markdown",
"content": "## A simple note\nA simple markdown `note`"
}

View file

@ -0,0 +1,5 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"name": "A simple note"
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"nameMap": {
"en": "A simple note",
"es": "Una nota sencilla",
"zh-Hans": "一段简单的笔记"
}
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Video",
"name": "Birds Flying",
"url": "http://example.org/video.mkv",
"duration": "PT2H"
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally added an object",
"type": "Add",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": "http://example.org/abc"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/image.png",
"height": 100,
"width": 100
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"mediaType": "text/html",
"name": "Previous"
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"hreflang": "en",
"mediaType": "text/html",
"name": "Previous"
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Page 1 of Sally's notes",
"type": "CollectionPage",
"id": "http://example.org/collection?page=1",
"partOf": "http://example.org/collection",
"items": [
{
"type": "Note",
"name": "Pizza Toppings to Try"
},
{
"type": "Note",
"name": "Thought about California"
}
]
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "Fresno Area",
"latitude": 36.75,
"longitude": 119.7667,
"radius": 15,
"units": "miles"
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "Fresno Area",
"latitude": 36.75,
"longitude": 119.7667,
"radius": 15,
"units": "miles"
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"hreflang": "en",
"mediaType": "text/html",
"name": "Next"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Event",
"name": "Going-Away Party for Jim",
"startTime": "2014-12-31T23:00:00-08:00",
"endTime": "2015-01-01T06:00:00-08:00"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "A simple note",
"type": "Note",
"content": "Fish swim.",
"published": "2014-12-12T12:12:12Z"
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Event",
"name": "Going-Away Party for Jim",
"startTime": "2014-12-31T23:00:00-08:00",
"endTime": "2015-01-01T06:00:00-08:00"
}

View file

@ -0,0 +1,22 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally added a picture of her cat to her cat picture collection",
"type": "Add",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Image",
"name": "A picture of my cat",
"url": "http://example.org/img/cat.png"
},
"origin": {
"type": "Collection",
"name": "Camera Roll"
},
"target": {
"type": "Collection",
"name": "My Cat Pictures"
}
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "Fresno Area",
"latitude": 36.75,
"longitude": 119.7667,
"radius": 15,
"units": "miles"
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"hreflang": "en",
"mediaType": "text/html",
"name": "Preview",
"rel": ["canonical", "preview"]
}

View file

@ -0,0 +1,16 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Page 1 of Sally's notes",
"type": "OrderedCollectionPage",
"startIndex": 0,
"orderedItems": [
{
"type": "Note",
"name": "Density of Water"
},
{
"type": "Note",
"name": "Air Mattress Idea"
}
]
}

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "Cane Sugar Processing",
"type": "Note",
"summary": "A simple <em>note</em>"
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "Cane Sugar Processing",
"type": "Note",
"summaryMap": {
"en": "A simple <em>note</em>",
"es": "Una <em>nota</em> sencilla",
"zh-Hans": "一段<em>简单的</em>笔记"
}
}

View file

@ -0,0 +1,16 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally's notes",
"type": "Collection",
"totalItems": 2,
"items": [
{
"type": "Note",
"name": "Which Staircase Should I Use"
},
{
"type": "Note",
"name": "Something to Remember"
}
]
}

View file

@ -0,0 +1,9 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "Fresno Area",
"latitude": 36.75,
"longitude": 119.7667,
"radius": 15,
"units": "miles"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "Cranberry Sauce Idea",
"type": "Note",
"content": "Mush it up so it does not have the same shape as the can.",
"updated": "2014-12-12T12:12:12Z"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/image.png",
"height": 100,
"width": 100
}

View file

@ -0,0 +1,14 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally is an acquaintance of John's",
"type": "Relationship",
"subject": {
"type": "Person",
"name": "Sally"
},
"relationship": "http://purl.org/vocab/relationship/acquaintanceOf",
"object": {
"type": "Person",
"name": "John"
}
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally arrived at work",
"type": "Arrive",
"actor": {
"type": "Person",
"name": "Sally"
},
"location": {
"type": "Place",
"name": "Work"
},
"origin": {
"type": "Place",
"name": "Home"
}
}

View file

@ -0,0 +1,14 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally is an acquaintance of John's",
"type": "Relationship",
"subject": {
"type": "Person",
"name": "Sally"
},
"relationship": "http://purl.org/vocab/relationship/acquaintanceOf",
"object": {
"type": "Person",
"name": "John"
}
}

View file

@ -0,0 +1,11 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally's profile",
"type": "Profile",
"describes": {
"type": "Person",
"name": "Sally"
},
"url": "http://sally.example.org"
}

View file

@ -0,0 +1,7 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "This image has been deleted",
"type": "Tombstone",
"formerType": "Image",
"url": "http://example.org/image/2"
}

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "This image has been deleted",
"type": "Tombstone",
"deleted": "2016-05-03T00:00:00Z"
}

View file

@ -0,0 +1,44 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Activities in Project XYZ",
"type": "Collection",
"items": [
{
"summary": "Sally created a note",
"type": "Create",
"id": "http://activities.example.com/1",
"actor": "http://sally.example.org",
"object": {
"summary": "A note",
"type": "Note",
"id": "http://notes.example.com/1",
"content": "A note"
},
"context": {
"type": "http://example.org/Project",
"name": "Project XYZ"
},
"audience": {
"type": "Group",
"name": "Project XYZ Working Group"
},
"to": "http://john.example.org"
},
{
"summary": "John liked Sally's note",
"type": "Like",
"id": "http://activities.example.com/1",
"actor": "http://john.example.org",
"object": "http://notes.example.com/1",
"context": {
"type": "http://example.org/Project",
"name": "Project XYZ"
},
"audience": {
"type": "Group",
"name": "Project XYZ Working Group"
},
"to": "http://sally.example.org"
}
]
}

View file

@ -0,0 +1,33 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally's friends list",
"type": "Collection",
"items": [
{
"summary": "Sally is influenced by Joe",
"type": "Relationship",
"subject": {
"type": "Person",
"name": "Sally"
},
"relationship": "http://purl.org/vocab/relationship/influencedBy",
"object": {
"type": "Person",
"name": "Joe"
}
},
{
"summary": "Sally is a friend of Jane",
"type": "Relationship",
"subject": {
"type": "Person",
"name": "Sally"
},
"relationship": "http://purl.org/vocab/relationship/friendOf",
"object": {
"type": "Person",
"name": "Jane"
}
}
]
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally became a friend of Matt",
"type": "Create",
"actor": "http://sally.example.org",
"object": {
"type": "Relationship",
"subject": "http://sally.example.org",
"relationship": "http://purl.org/vocab/relationship/friendOf",
"object": "http://matt.example.org",
"startTime": "2015-04-21T12:34:56"
}
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "http://example.org/connection-requests/123",
"summary": "Sally requested to be a friend of John",
"type": "Offer",
"actor": "acct:sally@example.org",
"object": {
"summary": "Sally and John's friendship",
"id": "http://example.org/connections/123",
"type": "Relationship",
"subject": "acct:sally@example.org",
"relationship": "http://purl.org/vocab/relationship/friendOf",
"object": "acct:john@example.org"
},
"target": "acct:john@example.org"
}

View file

@ -0,0 +1,62 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally and John's relationship history",
"type": "Collection",
"items": [
{
"summary": "John accepted Sally's friend request",
"id": "http://example.org/activities/122",
"type": "Accept",
"actor": "acct:john@example.org",
"object": "http://example.org/connection-requests/123",
"inReplyTo": "http://example.org/connection-requests/123",
"context": "http://example.org/connections/123",
"result": [
"http://example.org/activities/123",
"http://example.org/activities/124",
"http://example.org/activities/125",
"http://example.org/activities/126"
]
},
{
"summary": "John followed Sally",
"id": "http://example.org/activities/123",
"type": "Follow",
"actor": "acct:john@example.org",
"object": "acct:sally@example.org",
"context": "http://example.org/connections/123"
},
{
"summary": "Sally followed John",
"id": "http://example.org/activities/124",
"type": "Follow",
"actor": "acct:sally@example.org",
"object": "acct:john@example.org",
"context": "http://example.org/connections/123"
},
{
"summary": "John added Sally to his friends list",
"id": "http://example.org/activities/125",
"type": "Add",
"actor": "acct:john@example.org",
"object": "http://example.org/connections/123",
"target": {
"type": "Collection",
"summary": "John's Connections"
},
"context": "http://example.org/connections/123"
},
{
"summary": "Sally added John to her friends list",
"id": "http://example.org/activities/126",
"type": "Add",
"actor": "acct:sally@example.org",
"object": "http://example.org/connections/123",
"target": {
"type": "Collection",
"summary": "Sally's Connections"
},
"context": "http://example.org/connections/123"
}
]
}

View file

@ -0,0 +1,5 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "San Francisco, CA"
}

View file

@ -0,0 +1,14 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally created a note",
"type": "Create",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Note",
"name": "A Simple Note",
"content": "This is a simple note"
}
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": "San Francisco, CA",
"longitude": "122.4167",
"latitude": "37.7833"
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "A question about robots",
"id": "http://help.example.org/question/1",
"type": "Question",
"content": "I'd like to build a robot to feed my cat. Should I use Arduino or Raspberry Pi?"
}

View file

@ -0,0 +1,11 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "http://polls.example.org/question/1",
"name": "A question about robots",
"type": "Question",
"content": "I'd like to build a robot to feed my cat. Which platform is best?",
"oneOf": [
{"name": "arduino"},
{"name": "raspberry pi"}
]
}

View file

@ -0,0 +1,6 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"attributedTo": "http://sally.example.org",
"inReplyTo": "http://polls.example.org/question/1",
"name": "arduino"
}

View file

@ -0,0 +1,36 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "A question about robots",
"id": "http://polls.example.org/question/1",
"type": "Question",
"content": "I'd like to build a robot to feed my cat. Which platform is best?",
"oneOf": [
{"name": "arduino"},
{"name": "raspberry pi"}
],
"replies": {
"type": "Collection",
"totalItems": 3,
"items": [
{
"attributedTo": "http://sally.example.org",
"inReplyTo": "http://polls.example.org/question/1",
"name": "arduino"
},
{
"attributedTo": "http://joe.example.org",
"inReplyTo": "http://polls.example.org/question/1",
"name": "arduino"
},
{
"attributedTo": "http://john.example.org",
"inReplyTo": "http://polls.example.org/question/1",
"name": "raspberry pi"
}
]
},
"result": {
"type": "Note",
"content": "Users are favoriting &quot;arduino&quot; by a 33% margin."
}
}

View file

@ -0,0 +1,35 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "History of John's note",
"type": "Collection",
"items": [
{
"summary": "Sally liked John's note",
"type": "Like",
"actor": "http://sally.example.org",
"id": "http://activities.example.com/1",
"published": "2015-11-12T12:34:56Z",
"object": {
"summary": "John's note",
"type": "Note",
"id": "http://notes.example.com/1",
"attributedTo": "http://john.example.org",
"content": "My note"
}
},
{
"summary": "Sally disliked John's note",
"type": "Dislike",
"actor": "http://sally.example.org",
"id": "http://activities.example.com/2",
"published": "2015-12-11T21:43:56Z",
"object": {
"summary": "John's note",
"type": "Note",
"id": "http://notes.example.com/1",
"attributedTo": "http://john.example.org",
"content": "My note"
}
}
]
}

View file

@ -0,0 +1,29 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "History of John's note",
"type": "Collection",
"items": [
{
"summary": "Sally liked John's note",
"type": "Like",
"id": "http://activities.example.com/1",
"actor": "http://sally.example.org",
"published": "2015-11-12T12:34:56Z",
"object": {
"summary": "John's note",
"type": "Note",
"id": "http://notes.example.com/1",
"attributedTo": "http://john.example.org",
"content": "My note"
}
},
{
"summary": "Sally no longer likes John's note",
"type": "Undo",
"id": "http://activities.example.com/2",
"actor": "http://sally.example.org",
"published": "2015-12-11T21:43:56Z",
"object": "http://activities.example.com/1"
}
]
}

View file

@ -0,0 +1,15 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "A thank-you note",
"type": "Note",
"content": "Thank you <a href='http://sally.example.org'>@sally</a> for all your hard work! <a href='http://example.org/tags/givingthanks'>#givingthanks</a>",
"to": {
"name": "Sally",
"type": "Person",
"id": "http://sally.example.org"
},
"tag": {
"id": "http://example.org/tags/givingthanks",
"name": "#givingthanks"
}
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"name": "A thank-you note",
"type": "Note",
"content": "Thank you @sally for all your hard work! #givingthanks",
"tag": [
{
"type": "Mention",
"href": "http://example.org/people/sally",
"name": "@sally"
},
{
"id": "http://example.org/tags/givingthanks",
"name": "#givingthanks"
}
]
}

View file

@ -0,0 +1,18 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally moved the sales figures from Folder A to Folder B",
"type": "Move",
"actor": "http://sally.example.org",
"object": {
"type": "Document",
"name": "sales figures"
},
"origin": {
"type": "Collection",
"name": "Folder A"
},
"target": {
"type": "Collection",
"name": "Folder B"
}
}

View file

@ -0,0 +1,14 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally deleted a note",
"type": "Delete",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": "http://example.org/notes/1",
"origin": {
"type": "Collection",
"name": "Sally's Notes"
}
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally followed John",
"type": "Follow",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Person",
"name": "John"
}
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally ignored a note",
"type": "Ignore",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": "http://example.org/notes/1"
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally joined a group",
"type": "Join",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Group",
"name": "A Simple Group"
}
}

View file

@ -0,0 +1,8 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Link",
"href": "http://example.org/abc",
"hreflang": "en",
"mediaType": "text/html",
"name": "An example link"
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally left work",
"type": "Leave",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Place",
"name": "Work"
}
}

View file

@ -0,0 +1,13 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally left a group",
"type": "Leave",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Group",
"name": "A Simple Group"
}
}

View file

@ -0,0 +1,10 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally liked a note",
"type": "Like",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": "http://example.org/notes/1"
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally offered 50% off to Lewis",
"type": "Offer",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "http://www.types.example/ProductOffer",
"name": "50% Off!"
},
"target": {
"type": "Person",
"name": "Lewis"
}
}

View file

@ -0,0 +1,23 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally invited John and Lisa to a party",
"type": "Invite",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Event",
"name": "A Party"
},
"target": [
{
"type": "Person",
"name": "John"
},
{
"type": "Person",
"name": "Lisa"
}
]
}

View file

@ -0,0 +1,18 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally rejected an invitation to a party",
"type": "Reject",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Invite",
"actor": "http://john.example.org",
"object": {
"type": "Event",
"name": "Going-Away Party for Jim"
}
}
}

View file

@ -0,0 +1,18 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally tentatively rejected an invitation to a party",
"type": "TentativeReject",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": {
"type": "Invite",
"actor": "http://john.example.org",
"object": {
"type": "Event",
"name": "Going-Away Party for Jim"
}
}
}

View file

@ -0,0 +1,14 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "Sally removed a note from her notes folder",
"type": "Remove",
"actor": {
"type": "Person",
"name": "Sally"
},
"object": "http://example.org/notes/1",
"target": {
"type": "Collection",
"name": "Notes Folder"
}
}

View file

@ -0,0 +1,17 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": "The moderator removed Sally from a group",
"type": "Remove",
"actor": {
"type": "http://example.org/Role",
"name": "The Moderator"
},
"object": {
"type": "Person",
"name": "Sally"
},
"origin": {
"type": "Group",
"name": "A Simple Group"
}
}

Some files were not shown because too many files have changed in this diff Show more