Add support for remote interact (#519)

Add support for remote interaction ([this thing](https://eldritch.cafe/users/Barmaid/remote_follow) in mastodon)

- [x] create the endpoint dispatching remote interactions to local pages
- [x] add this endpoint to web-finger
- [x] propose remote interaction when following and not connected
- [x] propose remote interaction when liking/sharing and not connected
- [x] propose remote interaction when commenting and not connected
- [x] fetch posts/comments we don't know but remote interaction was requested for ?
This commit is contained in:
fdb-hiroshima
2019-04-17 22:09:07 +02:00
committed by Baptiste Gelez
parent 12efe721cc
commit 0d708e1639
28 changed files with 1173 additions and 481 deletions
+6
View File
@@ -27,6 +27,8 @@ extern crate rocket;
extern crate rocket_contrib;
extern crate rocket_csrf;
extern crate rocket_i18n;
#[macro_use]
extern crate runtime_fmt;
extern crate scheduled_thread_pool;
extern crate serde;
#[macro_use]
@@ -162,6 +164,7 @@ Then try to restart Plume
routes::instance::toggle_block,
routes::instance::update_settings,
routes::instance::shared_inbox,
routes::instance::interact,
routes::instance::nodeinfo,
routes::instance::about,
routes::instance::web_manifest,
@@ -183,6 +186,8 @@ Then try to restart Plume
routes::posts::new_auth,
routes::posts::create,
routes::posts::delete,
routes::posts::remote_interact,
routes::posts::remote_interact_post,
routes::reshares::create,
routes::reshares::create_auth,
routes::search::search,
@@ -207,6 +212,7 @@ Then try to restart Plume
routes::user::update,
routes::user::delete,
routes::user::follow,
routes::user::follow_not_connected,
routes::user::follow_auth,
routes::user::activity_details,
routes::user::outbox,
+31
View File
@@ -8,6 +8,7 @@ use serde_json;
use validator::{Validate, ValidationErrors};
use inbox;
use plume_common::activity_pub::inbox::FromId;
use plume_models::{
admin::Admin, comments::Comment, db_conn::DbConn, headers::Headers, instance::*, posts::Post,
safe_string::SafeString, users::User, Error, PlumeRocket, CONFIG,
@@ -217,6 +218,36 @@ pub fn shared_inbox(
inbox::handle_incoming(rockets, data, headers)
}
#[get("/remote_interact?<target>")]
pub fn interact(rockets: PlumeRocket, user: Option<User>, target: String) -> Option<Redirect> {
if User::find_by_fqn(&rockets, &target).is_ok() {
return Some(Redirect::to(uri!(super::user::details: name = target)));
}
if let Ok(post) = Post::from_id(&rockets, &target, None) {
return Some(Redirect::to(
uri!(super::posts::details: blog = post.get_blog(&rockets.conn).expect("Can't retrieve blog").fqn, slug = &post.slug, responding_to = _),
));
}
if let Ok(comment) = Comment::from_id(&rockets, &target, None) {
if comment.can_see(&rockets.conn, user.as_ref()) {
let post = comment
.get_post(&rockets.conn)
.expect("Can't retrieve post");
return Some(Redirect::to(uri!(
super::posts::details: blog = post
.get_blog(&rockets.conn)
.expect("Can't retrieve blog")
.fqn,
slug = &post.slug,
responding_to = comment.id
)));
}
}
None
}
#[get("/nodeinfo/<version>")]
pub fn nodeinfo(conn: DbConn, version: String) -> Result<Json<serde_json::Value>, ErrorPage> {
if version != "2.0" && version != "2.1" {
+6
View File
@@ -71,6 +71,12 @@ impl Default for Page {
}
}
/// A form for remote interaction, used by multiple routes
#[derive(Clone, Default, FromForm)]
pub struct RemoteForm {
pub remote: String,
}
pub fn post_to_atom(post: Post, conn: &Connection) -> Entry {
EntryBuilder::default()
.title(format!("<![CDATA[{}]]>", post.title))
+54 -1
View File
@@ -26,7 +26,7 @@ use plume_models::{
users::User,
Error, PlumeRocket,
};
use routes::{comments::NewCommentForm, errors::ErrorPage, ContentLen};
use routes::{comments::NewCommentForm, errors::ErrorPage, ContentLen, RemoteForm};
use template_utils::Ructe;
#[get("/~/<blog>/<slug>?<responding_to>", rank = 4)]
@@ -588,3 +588,56 @@ pub fn delete(
))
}
}
#[get("/~/<blog_name>/<slug>/remote_interact")]
pub fn remote_interact(
rockets: PlumeRocket,
blog_name: String,
slug: String,
i18n: I18n,
) -> Result<Ructe, ErrorPage> {
let target = Blog::find_by_fqn(&rockets, &blog_name)
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
Ok(render!(posts::remote_interact(
&(&rockets.conn, &i18n.catalog, None),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
RemoteForm::default(),
ValidationErrors::default()
)))
}
#[post("/~/<blog_name>/<slug>/remote_interact", data = "<remote>")]
pub fn remote_interact_post(
rockets: PlumeRocket,
blog_name: String,
slug: String,
remote: LenientForm<RemoteForm>,
i18n: I18n,
) -> Result<Result<Ructe, Redirect>, ErrorPage> {
let target = Blog::find_by_fqn(&rockets, &blog_name)
.and_then(|blog| Post::find_by_slug(&rockets.conn, &slug, blog.id))?;
if let Some(uri) = User::fetch_remote_interact_uri(&remote.remote)
.ok()
.and_then(|uri| rt_format!(uri, uri = target.ap_url).ok())
{
Ok(Err(Redirect::to(uri)))
} else {
let mut errs = ValidationErrors::new();
errs.add("remote", ValidationError {
code: Cow::from("invalid_remote"),
message: Some(Cow::from(i18n!(&i18n.catalog, "Couldn't obtain enough information about your account. Please make sure your username is correct."))),
params: HashMap::new(),
});
//could not get your remote url?
Ok(Ok(render!(posts::remote_interact(
&(&rockets.conn, &i18n.catalog, None),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
remote.clone(),
errs
))))
}
}
+66 -2
View File
@@ -25,7 +25,7 @@ use plume_models::{
users::*,
Error, PlumeRocket,
};
use routes::{errors::ErrorPage, Page};
use routes::{errors::ErrorPage, Page, RemoteForm};
use template_utils::Ructe;
#[get("/me")]
@@ -173,7 +173,71 @@ pub fn follow(name: String, user: User, rockets: PlumeRocket) -> Result<Redirect
Ok(Redirect::to(uri!(details: name = name)))
}
#[post("/@/<name>/follow", rank = 2)]
#[post("/@/<name>/follow", data = "<remote_form>", rank = 2)]
pub fn follow_not_connected(
rockets: PlumeRocket,
name: String,
remote_form: Option<LenientForm<RemoteForm>>,
i18n: I18n,
) -> Result<Result<Flash<Ructe>, Redirect>, ErrorPage> {
let target = User::find_by_fqn(&rockets, &name)?;
if let Some(remote_form) = remote_form {
let remote = &remote_form.remote;
if let Some(uri) = User::fetch_remote_interact_uri(remote)
.ok()
.and_then(|uri| {
rt_format!(
uri,
uri = format!(
"{}@{}",
target.fqn,
target.get_instance(&rockets.conn).ok()?.public_domain
)
)
.ok()
})
{
Ok(Err(Redirect::to(uri)))
} else {
let mut err = ValidationErrors::default();
err.add("remote",
ValidationError {
code: Cow::from("invalid_remote"),
message: Some(Cow::from(i18n!(&i18n.catalog, "Couldn't obtain enough information about your account. Please make sure your username is correct."))),
params: HashMap::new(),
},
);
Ok(Ok(Flash::new(
render!(users::follow_remote(
&(&rockets.conn, &i18n.catalog, None),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
remote_form.clone(),
err
)),
"callback",
uri!(follow: name = name).to_string(),
)))
}
} else {
Ok(Ok(Flash::new(
render!(users::follow_remote(
&(&rockets.conn, &i18n.catalog, None),
target,
super::session::LoginForm::default(),
ValidationErrors::default(),
#[allow(clippy::map_clone)]
remote_form.map(|x| x.clone()).unwrap_or_default(),
ValidationErrors::default()
)),
"callback",
uri!(follow: name = name).to_string(),
)))
}
}
#[get("/@/<name>/follow?local", rank = 2)]
pub fn follow_auth(name: String, i18n: I18n) -> Flash<Redirect> {
utils::requires_login(
&i18n!(