mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-25 22:01:19 +00:00
Merge branch 'main' into simplify-config
This commit is contained in:
commit
65acc29ae4
97 changed files with 2135 additions and 2195 deletions
74
Cargo.lock
generated
74
Cargo.lock
generated
|
@ -81,12 +81,10 @@ dependencies = [
|
|||
"ahash 0.7.4",
|
||||
"base64 0.13.0",
|
||||
"bitflags",
|
||||
"brotli2",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"derive_more",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
|
@ -108,7 +106,6 @@ dependencies = [
|
|||
"smallvec",
|
||||
"time 0.2.27",
|
||||
"tokio",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -398,7 +395,6 @@ dependencies = [
|
|||
"base64 0.13.0",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"cookie",
|
||||
"derive_more",
|
||||
"futures-core",
|
||||
"itoa",
|
||||
|
@ -565,26 +561,6 @@ dependencies = [
|
|||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli2"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
|
||||
dependencies = [
|
||||
"brotli-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.7.0"
|
||||
|
@ -649,9 +625,6 @@ name = "cc"
|
|||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -729,9 +702,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf8865bac3d9a3bde5bde9088ca431b11f5d37c7a578b8086af77248b76627"
|
||||
checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.2.27",
|
||||
|
@ -1367,9 +1340,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http-signature-normalization-actix"
|
||||
version = "0.5.0-beta.6"
|
||||
version = "0.5.0-beta.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fddb5f0de5059c337a00cfe5b224768e1821327da9df4b6f27762cbf305126d"
|
||||
checksum = "aa7cf7b03512ba7341b4252794751c5ff46635e0ff33eb864a929a5b7381e17a"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"awc",
|
||||
|
@ -1531,15 +1504,6 @@ version = "0.4.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
|
@ -1751,6 +1715,7 @@ dependencies = [
|
|||
"lemmy_utils",
|
||||
"lemmy_websocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -3657,32 +3622,3 @@ name = "xdg"
|
|||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.7.0+zstd.1.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9428752481d8372e15b1bf779ea518a179ad6c771cca2d2c60e4fbff3cc2cd52"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "3.1.0+zstd.1.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa1926623ad7fe406e090555387daf73db555b948134b4d73eac5eb08fb666d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "1.5.0+zstd.1.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e6c094340240369025fc6b731b054ee2a834328fa584310ac96aa4baebdc465"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
|
|
@ -51,7 +51,7 @@ env_logger = "0.8.4"
|
|||
strum = "0.21.0"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
openssl = "0.10.35"
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.6", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.7", default-features = false, features = ["sha-2"] }
|
||||
tokio = { version = "1.8.0", features = ["sync"] }
|
||||
anyhow = "1.0.41"
|
||||
reqwest = { version = "0.11.4", features = ["json"] }
|
||||
|
|
66
RELEASES.md
66
RELEASES.md
|
@ -1,3 +1,68 @@
|
|||
# Lemmy v0.11.3 Release (2021-07-30)
|
||||
|
||||
## Changes
|
||||
|
||||
Since our last release, we've had [~30](https://github.com/LemmyNet/lemmy/compare/0.11.0...main) commits to Lemmy, and [~60](https://github.com/LemmyNet/lemmy-ui/compare/0.11.0...main) to Lemmy UI.
|
||||
|
||||
### Lemmy Server
|
||||
|
||||
- Blank out extra info for deleted or removed content. Fixes [#1679](https://github.com/LemmyNet/Lemmy/issues/1679)
|
||||
- Add show_new_posts_notifs setting. Fixes [#1664](https://github.com/LemmyNet/Lemmy/issues/1664)
|
||||
- Fix issue with protocol string in actor id generation [#1668](https://github.com/LemmyNet/Lemmy/issues/1668)
|
||||
- Adding shortname fetching for users and communities. Fixes [#1662](https://github.com/LemmyNet/Lemmy/issues/1662)
|
||||
- Make captcha case-insensitive.
|
||||
- Remove tracking params from post url (fixes [#768](https://github.com/LemmyNet/Lemmy/issues/768))
|
||||
- Upgrade pictrs. Fixes [#1599](https://github.com/LemmyNet/Lemmy/issues/1599)
|
||||
- Invalidate current logins on account deletion. Fixes [#1602](https://github.com/LemmyNet/Lemmy/issues/1602)
|
||||
- Fix nsfw posts showing for non-logged in users. Fixes [#1614](https://github.com/LemmyNet/Lemmy/issues/1614)
|
||||
- Add additional slurs configuration option. Closes [#1464](https://github.com/LemmyNet/Lemmy/issues/1464).
|
||||
- Updating to rust 1.51.0
|
||||
|
||||
### Lemmy UI
|
||||
|
||||
- Have setting to disable notifs for new posts. Fixes [#132](https://github.com/LemmyNet/lemmy-ui/issues/132)
|
||||
- Remove max length constraints on actors. Fixes [#350](https://github.com/LemmyNet/lemmy-ui/issues/350)
|
||||
- Fix captcha replay bug. Fixes [#348](https://github.com/LemmyNet/lemmy-ui/issues/348)
|
||||
- Removing community and user routes in favor of shortnames. Fixes [#317](https://github.com/LemmyNet/lemmy-ui/issues/317)
|
||||
- Add front end helpers 1 [(#346](https://github.com/LemmyNet/lemmy-ui/issues/346))
|
||||
- Don't use default subscribed for communities page.
|
||||
- Adding Listing type to communities page, default local. [#190](https://github.com/LemmyNet/lemmy-ui/issues/190)
|
||||
- Fix language bug on mobile browsers.
|
||||
- Collapse sidebar on mobile. Fixes [#335](https://github.com/LemmyNet/lemmy-ui/issues/335)
|
||||
- Re-organized components folder. [(#339](https://github.com/LemmyNet/lemmy-ui/issues/339))
|
||||
- Moving comment link to top bar. Fixes [#307](https://github.com/LemmyNet/lemmy-ui/issues/307)
|
||||
- Make spinner bigger. Fixes [#203](https://github.com/LemmyNet/lemmy-ui/issues/203)
|
||||
- Fix preview description html. Fixes [#110](https://github.com/LemmyNet/lemmy-ui/issues/110)
|
||||
- Update darkly, make danger darker. Fixes [#16](https://github.com/LemmyNet/lemmy-ui/issues/16)
|
||||
- Always show previous paginator, extract paginator component.
|
||||
- Use better comment collapse icon, and add text. Fixes [#318](https://github.com/LemmyNet/lemmy-ui/issues/318)
|
||||
- Fix symbols issue. Fixes [#319](https://github.com/LemmyNet/lemmy-ui/issues/319)
|
||||
- Don't restore scroll position on page refresh. Fixes [#186](https://github.com/LemmyNet/lemmy-ui/issues/186)
|
||||
- Insert triple backticks for 'code' button when multiple lines are selected. [(#311](https://github.com/LemmyNet/lemmy-ui/issues/311))
|
||||
- Adding a comment here placeholder. Fixes [#301](https://github.com/LemmyNet/lemmy-ui/issues/301)
|
||||
- Fix non-local community and person links. Fixes [#290](https://github.com/LemmyNet/lemmy-ui/issues/290)
|
||||
- Fix navbar bug. Fixes [#289](https://github.com/LemmyNet/lemmy-ui/issues/289)
|
||||
- Hide names of mods / admins without priveleges. Fixes [#285](https://github.com/LemmyNet/lemmy-ui/issues/285)
|
||||
- Adding URL search type. Fixes [#286](https://github.com/LemmyNet/lemmy-ui/issues/286)
|
||||
- Add a link to joinlemmy on lemmy.ml signup. Fixes [#235](https://github.com/LemmyNet/lemmy-ui/issues/235)
|
||||
- Fix duped site description. Fixes [#281](https://github.com/LemmyNet/lemmy-ui/issues/281)
|
||||
|
||||
### API
|
||||
|
||||
- Added `show_new_posts_notifs` boolean to `SaveUserSettings`, and `LocalUserSettings`.
|
||||
- A full list of the API changes can be seen on this diff of [lemmy-js-client: 0.11.0 -> 0.11.3](https://github.com/LemmyNet/lemmy-js-client/compare/0.11.0...0.11.3-rc.4) .
|
||||
|
||||
### Federation
|
||||
|
||||
- No changes in this release, but there will be many soon.
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
To upgrade your instance to `0.11.3`, simply follow the instructions in the documentation:
|
||||
|
||||
- [Upgrade with manual Docker installation](https://join-lemmy.org/docs/en/administration/install_docker.html#updating)
|
||||
- [Upgrade with Ansible installation](https://join-lemmy.org/docs/en/administration/install_ansible.html)
|
||||
|
||||
# Lemmy v0.11.0 Release (2021-04-27)
|
||||
|
||||
## Changes
|
||||
|
@ -49,7 +114,6 @@ Since our last release this month, we've had [~60](https://github.com/LemmyNet/l
|
|||
- Fix html notif bug. Fixes [#254](https://github.com/LemmyNet/lemmy-ui/issues/254)
|
||||
- Fixing issue with debounce. Fixes [#236](https://github.com/LemmyNet/lemmy-ui/issues/236)
|
||||
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
### Servers
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.11.2
|
||||
0.11.3
|
||||
|
|
|
@ -115,6 +115,7 @@ test('Delete a comment', async () => {
|
|||
commentRes.comment_view.comment.id
|
||||
);
|
||||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
||||
|
||||
// Make sure that comment is undefined on beta
|
||||
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
|
||||
|
@ -149,6 +150,7 @@ test('Remove a comment from admin and community on the same instance', async ()
|
|||
// The beta admin removes it (the community lives on beta)
|
||||
let removeCommentRes = await removeComment(beta, true, betaCommentId);
|
||||
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||
expect(removeCommentRes.comment_view.comment.content).toBe("");
|
||||
|
||||
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
|
||||
let refetchedPost = await getPost(alpha, postRes.post_view.post.id);
|
||||
|
|
|
@ -77,6 +77,7 @@ test('Delete community', async () => {
|
|||
communityRes.community_view.community.id
|
||||
);
|
||||
expect(deleteCommunityRes.community_view.community.deleted).toBe(true);
|
||||
expect(deleteCommunityRes.community_view.community.title).toBe("");
|
||||
|
||||
// Make sure it got deleted on A
|
||||
let communityOnAlphaDeleted = await getCommunity(
|
||||
|
@ -128,6 +129,7 @@ test('Remove community', async () => {
|
|||
communityRes.community_view.community.id
|
||||
);
|
||||
expect(removeCommunityRes.community_view.community.removed).toBe(true);
|
||||
expect(removeCommunityRes.community_view.community.title).toBe("");
|
||||
|
||||
// Make sure it got Removed on A
|
||||
let communityOnAlphaRemoved = await getCommunity(
|
||||
|
|
|
@ -210,6 +210,7 @@ test('Delete a post', async () => {
|
|||
|
||||
let deletedPost = await deletePost(alpha, true, postRes.post_view.post);
|
||||
expect(deletedPost.post_view.post.deleted).toBe(true);
|
||||
expect(deletedPost.post_view.post.name).toBe("");
|
||||
|
||||
// Make sure lemmy beta sees post is deleted
|
||||
let searchBeta = await searchPost(beta, postRes.post_view.post);
|
||||
|
@ -237,6 +238,7 @@ test('Remove a post from admin and community on different instance', async () =>
|
|||
|
||||
let removedPost = await removePost(alpha, true, postRes.post_view.post);
|
||||
expect(removedPost.post_view.post.removed).toBe(true);
|
||||
expect(removedPost.post_view.post.name).toBe("");
|
||||
|
||||
// Make sure lemmy beta sees post is NOT removed
|
||||
let searchBeta = await searchPost(beta, postRes.post_view.post);
|
||||
|
|
|
@ -64,6 +64,7 @@ test('Delete a private message', async () => {
|
|||
pmRes.private_message_view.private_message.id
|
||||
);
|
||||
expect(deletedPmRes.private_message_view.private_message.deleted).toBe(true);
|
||||
expect(deletedPmRes.private_message_view.private_message.content).toBe("");
|
||||
|
||||
// The GetPrivateMessages filters out deleted,
|
||||
// even though they are in the actual database.
|
||||
|
|
|
@ -35,7 +35,7 @@ lazy_static = "1.4.0"
|
|||
url = { version = "2.2.2", features = ["serde"] }
|
||||
openssl = "0.10.35"
|
||||
http = "0.2.4"
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.6", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.7", default-features = false, features = ["sha-2"] }
|
||||
base64 = "0.13.0"
|
||||
tokio = "1.8.0"
|
||||
futures = "0.3.15"
|
||||
|
|
|
@ -7,12 +7,19 @@ use lemmy_api_common::{
|
|||
comment::*,
|
||||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::ApubLikeableType;
|
||||
use lemmy_apub::{
|
||||
activities::voting::{
|
||||
undo_vote::UndoVote,
|
||||
vote::{Vote, VoteType},
|
||||
},
|
||||
PostOrComment,
|
||||
};
|
||||
use lemmy_db_queries::{source::comment::Comment_, Likeable, Saveable};
|
||||
use lemmy_db_schema::{source::comment::*, LocalUserId};
|
||||
use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkCommentAsRead {
|
||||
|
@ -170,6 +177,7 @@ impl Perform for CreateCommentLike {
|
|||
|
||||
// Only add the like if the score isnt 0
|
||||
let comment = orig_comment.comment;
|
||||
let object = PostOrComment::Comment(Box::new(comment));
|
||||
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
|
||||
if do_add {
|
||||
let like_form2 = like_form.clone();
|
||||
|
@ -178,16 +186,23 @@ impl Perform for CreateCommentLike {
|
|||
return Err(ApiError::err("couldnt_like_comment").into());
|
||||
}
|
||||
|
||||
if like_form.score == 1 {
|
||||
comment.send_like(&local_user_view.person, context).await?;
|
||||
} else if like_form.score == -1 {
|
||||
comment
|
||||
.send_dislike(&local_user_view.person, context)
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
orig_comment.community.id,
|
||||
like_form.score.try_into()?,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
comment
|
||||
.send_undo_like(&local_user_view.person, context)
|
||||
// API doesn't distinguish between Undo/Like and Undo/Dislike
|
||||
UndoVote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
orig_comment.community.id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,23 @@ use lemmy_api_common::{
|
|||
mark_post_as_read,
|
||||
post::*,
|
||||
};
|
||||
use lemmy_apub::{ApubLikeableType, ApubObjectType};
|
||||
use lemmy_apub::{
|
||||
activities::{
|
||||
post::create_or_update::CreateOrUpdatePost,
|
||||
voting::{
|
||||
undo_vote::UndoVote,
|
||||
vote::{Vote, VoteType},
|
||||
},
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
PostOrComment,
|
||||
};
|
||||
use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
|
||||
use lemmy_db_schema::source::{moderator::*, post::*};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for CreatePostLike {
|
||||
|
@ -50,6 +61,9 @@ impl Perform for CreatePostLike {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let object = PostOrComment::Post(Box::new(post));
|
||||
|
||||
// Only add the like if the score isnt 0
|
||||
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
|
||||
if do_add {
|
||||
|
@ -59,14 +73,23 @@ impl Perform for CreatePostLike {
|
|||
return Err(ApiError::err("couldnt_like_post").into());
|
||||
}
|
||||
|
||||
if like_form.score == 1 {
|
||||
post.send_like(&local_user_view.person, context).await?;
|
||||
} else if like_form.score == -1 {
|
||||
post.send_dislike(&local_user_view.person, context).await?;
|
||||
}
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
community_id,
|
||||
like_form.score.try_into()?,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
post
|
||||
.send_undo_like(&local_user_view.person, context)
|
||||
// API doesn't distinguish between Undo/Like and Undo/Dislike
|
||||
UndoVote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
community_id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
@ -140,8 +163,12 @@ impl Perform for LockPost {
|
|||
blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
|
||||
|
||||
// apub updates
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
CreateOrUpdatePost::send(
|
||||
&updated_post,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Refetch the post
|
||||
|
@ -214,8 +241,12 @@ impl Perform for StickyPost {
|
|||
|
||||
// Apub updates
|
||||
// TODO stickied should pry work like locked for ease of use
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
CreateOrUpdatePost::send(
|
||||
&updated_post,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Refetch the post
|
||||
|
|
|
@ -15,6 +15,7 @@ use lemmy_db_queries::{
|
|||
from_opt_str_to_opt_enum,
|
||||
source::site::Site_,
|
||||
Crud,
|
||||
DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SearchType,
|
||||
SortType,
|
||||
|
@ -332,6 +333,28 @@ impl Perform for Search {
|
|||
}
|
||||
};
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(SearchResponse {
|
||||
type_: search_type.to_string(),
|
||||
|
|
|
@ -18,7 +18,7 @@ lemmy_utils = { path = "../utils" }
|
|||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
log = "0.4.14"
|
||||
diesel = "1.4.7"
|
||||
actix-web = "4.0.0-beta.8"
|
||||
actix-web = {version = "4.0.0-beta.8", default-features = false, features = ["cookies"] }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||
url = "2.2.2"
|
||||
|
|
|
@ -30,7 +30,7 @@ lazy_static = "1.4.0"
|
|||
url = { version = "2.2.2", features = ["serde"] }
|
||||
openssl = "0.10.35"
|
||||
http = "0.2.4"
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.6", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.7", default-features = false, features = ["sha-2"] }
|
||||
base64 = "0.13.0"
|
||||
tokio = "1.8.0"
|
||||
futures = "0.3.15"
|
||||
|
|
|
@ -8,7 +8,16 @@ use lemmy_api_common::{
|
|||
get_post,
|
||||
send_local_notifs,
|
||||
};
|
||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||
use lemmy_apub::{
|
||||
activities::{
|
||||
comment::create_or_update::CreateOrUpdateComment,
|
||||
voting::vote::{Vote, VoteType},
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
generate_apub_endpoint,
|
||||
EndpointType,
|
||||
PostOrComment,
|
||||
};
|
||||
use lemmy_db_queries::{source::comment::Comment_, Crud, Likeable};
|
||||
use lemmy_db_schema::source::comment::*;
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
|
@ -37,8 +46,9 @@ impl PerformCrud for CreateComment {
|
|||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post = get_post(post_id, context.pool()).await?;
|
||||
let community_id = post.community_id;
|
||||
|
||||
check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
|
||||
check_community_ban(local_user_view.person.id, community_id, context.pool()).await?;
|
||||
|
||||
// Check if post is locked, no new comments
|
||||
if post.locked {
|
||||
|
@ -83,8 +93,12 @@ impl PerformCrud for CreateComment {
|
|||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_create_comment"))?;
|
||||
|
||||
updated_comment
|
||||
.send_create(&local_user_view.person, context)
|
||||
CreateOrUpdateComment::send(
|
||||
&updated_comment,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Create,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
|
@ -113,8 +127,14 @@ impl PerformCrud for CreateComment {
|
|||
return Err(ApiError::err("couldnt_like_comment").into());
|
||||
}
|
||||
|
||||
updated_comment
|
||||
.send_like(&local_user_view.person, context)
|
||||
let object = PostOrComment::Comment(Box::new(updated_comment));
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
community_id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
|
|
|
@ -9,7 +9,7 @@ use lemmy_api_common::{
|
|||
send_local_notifs,
|
||||
};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::{source::comment::Comment_, Crud};
|
||||
use lemmy_db_queries::{source::comment::Comment_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::{comment::*, moderator::*};
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
|
@ -47,7 +47,7 @@ impl PerformCrud for DeleteComment {
|
|||
|
||||
// Do the delete
|
||||
let deleted = data.deleted;
|
||||
let updated_comment = blocking(context.pool(), move |conn| {
|
||||
let mut updated_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::update_deleted(conn, comment_id, deleted)
|
||||
})
|
||||
.await?
|
||||
|
@ -55,6 +55,7 @@ impl PerformCrud for DeleteComment {
|
|||
|
||||
// Send the apub message
|
||||
if deleted {
|
||||
updated_comment = updated_comment.blank_out_deleted_or_removed_info();
|
||||
updated_comment
|
||||
.send_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
@ -67,11 +68,16 @@ impl PerformCrud for DeleteComment {
|
|||
// Refetch it
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
let mut comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if deleted {
|
||||
comment_view.comment = comment_view.comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
// Build the recipients
|
||||
let comment_view_2 = comment_view.clone();
|
||||
let mentions = vec![];
|
||||
|
@ -136,7 +142,7 @@ impl PerformCrud for RemoveComment {
|
|||
|
||||
// Do the remove
|
||||
let removed = data.removed;
|
||||
let updated_comment = blocking(context.pool(), move |conn| {
|
||||
let mut updated_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::update_removed(conn, comment_id, removed)
|
||||
})
|
||||
.await?
|
||||
|
@ -156,6 +162,7 @@ impl PerformCrud for RemoveComment {
|
|||
|
||||
// Send the apub message
|
||||
if removed {
|
||||
updated_comment = updated_comment.blank_out_deleted_or_removed_info();
|
||||
updated_comment
|
||||
.send_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
|
@ -168,11 +175,16 @@ impl PerformCrud for RemoveComment {
|
|||
// Refetch it
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
let mut comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if removed {
|
||||
comment_view.comment = comment_view.comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
// Build the recipients
|
||||
let comment_view_2 = comment_view.clone();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::PerformCrud;
|
|||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{blocking, comment::*, get_local_user_view_from_jwt_opt};
|
||||
use lemmy_apub::{build_actor_id_from_shortname, EndpointType};
|
||||
use lemmy_db_queries::{from_opt_str_to_opt_enum, ListingType, SortType};
|
||||
use lemmy_db_queries::{from_opt_str_to_opt_enum, DeleteableOrRemoveable, ListingType, SortType};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
@ -36,7 +36,7 @@ impl PerformCrud for GetComments {
|
|||
let saved_only = data.saved_only;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let comments = blocking(context.pool(), move |conn| {
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
|
@ -52,6 +52,14 @@ impl PerformCrud for GetComments {
|
|||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_get_comments"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
Ok(GetCommentsResponse { comments })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,11 @@ use lemmy_api_common::{
|
|||
get_local_user_view_from_jwt,
|
||||
send_local_notifs,
|
||||
};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::source::comment::Comment_;
|
||||
use lemmy_apub::activities::{
|
||||
comment::create_or_update::CreateOrUpdateComment,
|
||||
CreateOrUpdateType,
|
||||
};
|
||||
use lemmy_db_queries::{source::comment::Comment_, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::comment::*;
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{
|
||||
|
@ -59,8 +62,12 @@ impl PerformCrud for EditComment {
|
|||
.map_err(|_| ApiError::err("couldnt_update_comment"))?;
|
||||
|
||||
// Send the apub update
|
||||
updated_comment
|
||||
.send_update(&local_user_view.person, context)
|
||||
CreateOrUpdateComment::send(
|
||||
&updated_comment,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Do the mentions / recipients
|
||||
|
@ -78,11 +85,16 @@ impl PerformCrud for EditComment {
|
|||
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
let mut comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if comment_view.comment.deleted || comment_view.comment.removed {
|
||||
comment_view.comment = comment_view.comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids,
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{community::send_community_websocket, PerformCrud};
|
|||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt, is_admin};
|
||||
use lemmy_apub::CommunityType;
|
||||
use lemmy_db_queries::{source::community::Community_, Crud};
|
||||
use lemmy_db_queries::{source::community::Community_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::{
|
||||
community::*,
|
||||
moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
|
||||
|
@ -50,6 +50,7 @@ impl PerformCrud for DeleteCommunity {
|
|||
// Send apub messages
|
||||
if deleted {
|
||||
updated_community
|
||||
.blank_out_deleted_or_removed_info()
|
||||
.send_delete(local_user_view.person.to_owned(), context)
|
||||
.await?;
|
||||
} else {
|
||||
|
@ -60,11 +61,16 @@ impl PerformCrud for DeleteCommunity {
|
|||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if deleted {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = CommunityResponse { community_view };
|
||||
|
||||
send_community_websocket(
|
||||
|
@ -118,18 +124,26 @@ impl PerformCrud for RemoveCommunity {
|
|||
|
||||
// Apub messages
|
||||
if removed {
|
||||
updated_community.send_remove(context).await?;
|
||||
updated_community
|
||||
.blank_out_deleted_or_removed_info()
|
||||
.send_remove(context)
|
||||
.await?;
|
||||
} else {
|
||||
updated_community.send_undo_remove(context).await?;
|
||||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if removed {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = CommunityResponse { community_view };
|
||||
|
||||
send_community_websocket(
|
||||
|
|
|
@ -2,7 +2,13 @@ use crate::PerformCrud;
|
|||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt};
|
||||
use lemmy_apub::{build_actor_id_from_shortname, EndpointType};
|
||||
use lemmy_db_queries::{from_opt_str_to_opt_enum, ApubObject, ListingType, SortType};
|
||||
use lemmy_db_queries::{
|
||||
from_opt_str_to_opt_enum,
|
||||
ApubObject,
|
||||
DeleteableOrRemoveable,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_schema::source::community::*;
|
||||
use lemmy_db_views_actor::{
|
||||
community_moderator_view::CommunityModeratorView,
|
||||
|
@ -39,12 +45,17 @@ impl PerformCrud for GetCommunity {
|
|||
}
|
||||
};
|
||||
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_find_community"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if community_view.community.deleted || community_view.community.removed {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let moderators: Vec<CommunityModeratorView> = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
})
|
||||
|
@ -93,7 +104,7 @@ impl PerformCrud for ListCommunities {
|
|||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let communities = blocking(context.pool(), move |conn| {
|
||||
let mut communities = blocking(context.pool(), move |conn| {
|
||||
CommunityQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
|
@ -105,6 +116,14 @@ impl PerformCrud for ListCommunities {
|
|||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in communities
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.community.deleted || cv.community.removed)
|
||||
{
|
||||
cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
// Return the jwt
|
||||
Ok(ListCommunitiesResponse { communities })
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
get_local_user_view_from_jwt,
|
||||
};
|
||||
use lemmy_apub::CommunityType;
|
||||
use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud};
|
||||
use lemmy_db_queries::{diesel_option_overwrite_to_url, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::community::{Community, CommunityForm},
|
||||
|
@ -78,11 +78,16 @@ impl PerformCrud for EditCommunity {
|
|||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if community_view.community.deleted || community_view.community.removed {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = CommunityResponse { community_view };
|
||||
|
||||
send_community_websocket(
|
||||
|
|
|
@ -7,7 +7,16 @@ use lemmy_api_common::{
|
|||
mark_post_as_read,
|
||||
post::*,
|
||||
};
|
||||
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
|
||||
use lemmy_apub::{
|
||||
activities::{
|
||||
post::create_or_update::CreateOrUpdatePost,
|
||||
voting::vote::{Vote, VoteType},
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
generate_apub_endpoint,
|
||||
EndpointType,
|
||||
PostOrComment,
|
||||
};
|
||||
use lemmy_db_queries::{source::post::Post_, Crud, Likeable};
|
||||
use lemmy_db_schema::source::post::*;
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
|
@ -45,6 +54,9 @@ impl PerformCrud for CreatePost {
|
|||
let data_url = data.url.as_ref();
|
||||
let (iframely_response, pictrs_thumbnail) =
|
||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
|
||||
let (embed_title, embed_description, embed_html) = iframely_response
|
||||
.map(|u| (u.title, u.description, u.html))
|
||||
.unwrap_or((None, None, None));
|
||||
|
||||
let post_form = PostForm {
|
||||
name: data.name.trim().to_owned(),
|
||||
|
@ -53,9 +65,9 @@ impl PerformCrud for CreatePost {
|
|||
community_id: data.community_id,
|
||||
creator_id: local_user_view.person.id,
|
||||
nsfw: data.nsfw,
|
||||
embed_title: iframely_response.title,
|
||||
embed_description: iframely_response.description,
|
||||
embed_html: iframely_response.html,
|
||||
embed_title,
|
||||
embed_description,
|
||||
embed_html,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
..PostForm::default()
|
||||
};
|
||||
|
@ -82,8 +94,12 @@ impl PerformCrud for CreatePost {
|
|||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_create_post"))?;
|
||||
|
||||
updated_post
|
||||
.send_create(&local_user_view.person, context)
|
||||
CreateOrUpdatePost::send(
|
||||
&updated_post,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Create,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// They like their own post by default
|
||||
|
@ -103,8 +119,14 @@ impl PerformCrud for CreatePost {
|
|||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
|
||||
updated_post
|
||||
.send_like(&local_user_view.person, context)
|
||||
let object = PostOrComment::Post(Box::new(updated_post));
|
||||
Vote::send(
|
||||
&object,
|
||||
&local_user_view.person,
|
||||
inserted_post.community_id,
|
||||
VoteType::Like,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Refetch the view
|
||||
|
|
|
@ -8,7 +8,7 @@ use lemmy_api_common::{
|
|||
post::*,
|
||||
};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::{source::post::Post_, Crud};
|
||||
use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::{moderator::*, post::*};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
|
@ -52,6 +52,7 @@ impl PerformCrud for DeletePost {
|
|||
// apub updates
|
||||
if deleted {
|
||||
updated_post
|
||||
.blank_out_deleted_or_removed_info()
|
||||
.send_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
|
@ -62,11 +63,15 @@ impl PerformCrud for DeletePost {
|
|||
|
||||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
let mut post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
if deleted {
|
||||
post_view.post = post_view.post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = PostResponse { post_view };
|
||||
|
||||
context.chat_server().do_send(SendPost {
|
||||
|
@ -132,6 +137,7 @@ impl PerformCrud for RemovePost {
|
|||
// apub updates
|
||||
if removed {
|
||||
updated_post
|
||||
.blank_out_deleted_or_removed_info()
|
||||
.send_remove(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
|
@ -143,11 +149,16 @@ impl PerformCrud for RemovePost {
|
|||
// Refetch the post
|
||||
let post_id = data.post_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
let mut post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if removed {
|
||||
post_view.post = post_view.post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = PostResponse { post_view };
|
||||
|
||||
context.chat_server().do_send(SendPost {
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::PerformCrud;
|
|||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, mark_post_as_read, post::*};
|
||||
use lemmy_apub::{build_actor_id_from_shortname, EndpointType};
|
||||
use lemmy_db_queries::{from_opt_str_to_opt_enum, ListingType, SortType};
|
||||
use lemmy_db_queries::{from_opt_str_to_opt_enum, DeleteableOrRemoveable, ListingType, SortType};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentQueryBuilder,
|
||||
post_view::{PostQueryBuilder, PostView},
|
||||
|
@ -32,19 +32,24 @@ impl PerformCrud for GetPost {
|
|||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let id = data.id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
let mut post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, id, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_find_post"))?;
|
||||
|
||||
// Blank out deleted info
|
||||
if post_view.post.deleted || post_view.post.removed {
|
||||
post_view.post = post_view.post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
// Mark the post as read
|
||||
if let Some(person_id) = person_id {
|
||||
mark_post_as_read(person_id, id, context.pool()).await?;
|
||||
}
|
||||
|
||||
let id = data.id;
|
||||
let comments = blocking(context.pool(), move |conn| {
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
|
@ -54,6 +59,14 @@ impl PerformCrud for GetPost {
|
|||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let community_id = post_view.community.id;
|
||||
let moderators = blocking(context.pool(), move |conn| {
|
||||
CommunityModeratorView::for_community(conn, community_id)
|
||||
|
@ -61,12 +74,17 @@ impl PerformCrud for GetPost {
|
|||
.await??;
|
||||
|
||||
// Necessary for the sidebar
|
||||
let community_view = blocking(context.pool(), move |conn| {
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_find_community"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if community_view.community.deleted || community_view.community.removed {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let online = context
|
||||
.chat_server()
|
||||
.send(GetPostUsersOnline { post_id: data.id })
|
||||
|
@ -119,7 +137,7 @@ impl PerformCrud for GetPosts {
|
|||
.unwrap_or(None);
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let posts = blocking(context.pool(), move |conn| {
|
||||
let mut posts = blocking(context.pool(), move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
|
@ -137,6 +155,14 @@ impl PerformCrud for GetPosts {
|
|||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_get_posts"))?;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for pv in posts
|
||||
.iter_mut()
|
||||
.filter(|p| p.post.deleted || p.post.removed)
|
||||
{
|
||||
pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
Ok(GetPostsResponse { posts })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::PerformCrud;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::{source::post::Post_, Crud};
|
||||
use lemmy_apub::activities::{post::create_or_update::CreateOrUpdatePost, CreateOrUpdateType};
|
||||
use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::{naive_now, source::post::*};
|
||||
use lemmy_db_views::post_view::PostView;
|
||||
use lemmy_utils::{
|
||||
|
@ -54,6 +54,9 @@ impl PerformCrud for EditPost {
|
|||
let data_url = data.url.as_ref();
|
||||
let (iframely_response, pictrs_thumbnail) =
|
||||
fetch_iframely_and_pictrs_data(context.client(), data_url).await?;
|
||||
let (embed_title, embed_description, embed_html) = iframely_response
|
||||
.map(|u| (u.title, u.description, u.html))
|
||||
.unwrap_or((None, None, None));
|
||||
|
||||
let post_form = PostForm {
|
||||
creator_id: orig_post.creator_id.to_owned(),
|
||||
|
@ -63,9 +66,9 @@ impl PerformCrud for EditPost {
|
|||
body: data.body.to_owned(),
|
||||
nsfw: data.nsfw,
|
||||
updated: Some(naive_now()),
|
||||
embed_title: iframely_response.title,
|
||||
embed_description: iframely_response.description,
|
||||
embed_html: iframely_response.html,
|
||||
embed_title,
|
||||
embed_description,
|
||||
embed_html,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
..PostForm::default()
|
||||
};
|
||||
|
@ -89,16 +92,25 @@ impl PerformCrud for EditPost {
|
|||
};
|
||||
|
||||
// Send apub update
|
||||
updated_post
|
||||
.send_update(&local_user_view.person, context)
|
||||
CreateOrUpdatePost::send(
|
||||
&updated_post,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
let post_view = blocking(context.pool(), move |conn| {
|
||||
let mut post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, post_id, Some(local_user_view.person.id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted info
|
||||
if post_view.post.deleted || post_view.post.removed {
|
||||
post_view.post = post_view.post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = PostResponse { post_view };
|
||||
|
||||
context.chat_server().do_send(SendPost {
|
||||
|
|
|
@ -6,7 +6,14 @@ use lemmy_api_common::{
|
|||
person::{CreatePrivateMessage, PrivateMessageResponse},
|
||||
send_email_to_user,
|
||||
};
|
||||
use lemmy_apub::{generate_apub_endpoint, ApubObjectType, EndpointType};
|
||||
use lemmy_apub::{
|
||||
activities::{
|
||||
private_message::create_or_update::CreateOrUpdatePrivateMessage,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
generate_apub_endpoint,
|
||||
EndpointType,
|
||||
};
|
||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||
use lemmy_db_schema::source::private_message::{PrivateMessage, PrivateMessageForm};
|
||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||
|
@ -63,8 +70,12 @@ impl PerformCrud for CreatePrivateMessage {
|
|||
.await?
|
||||
.map_err(|_| ApiError::err("couldnt_create_private_message"))?;
|
||||
|
||||
updated_private_message
|
||||
.send_create(&local_user_view.person, context)
|
||||
CreateOrUpdatePrivateMessage::send(
|
||||
&updated_private_message,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Create,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let private_message_view = blocking(context.pool(), move |conn| {
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
person::{DeletePrivateMessage, PrivateMessageResponse},
|
||||
};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||
use lemmy_utils::{ApiError, ConnectionId, LemmyError};
|
||||
|
@ -46,6 +46,7 @@ impl PerformCrud for DeletePrivateMessage {
|
|||
// Send the apub update
|
||||
if data.deleted {
|
||||
updated_private_message
|
||||
.blank_out_deleted_or_removed_info()
|
||||
.send_delete(&local_user_view.person, context)
|
||||
.await?;
|
||||
} else {
|
||||
|
@ -55,11 +56,18 @@ impl PerformCrud for DeletePrivateMessage {
|
|||
}
|
||||
|
||||
let private_message_id = data.private_message_id;
|
||||
let private_message_view = blocking(context.pool(), move |conn| {
|
||||
let mut private_message_view = blocking(context.pool(), move |conn| {
|
||||
PrivateMessageView::read(conn, private_message_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if deleted {
|
||||
private_message_view.private_message = private_message_view
|
||||
.private_message
|
||||
.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = PrivateMessageResponse {
|
||||
private_message_view,
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ use lemmy_api_common::{
|
|||
get_local_user_view_from_jwt,
|
||||
person::{GetPrivateMessages, PrivateMessagesResponse},
|
||||
};
|
||||
use lemmy_db_queries::DeleteableOrRemoveable;
|
||||
use lemmy_db_views::private_message_view::PrivateMessageQueryBuilder;
|
||||
use lemmy_utils::{ConnectionId, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
@ -25,7 +26,7 @@ impl PerformCrud for GetPrivateMessages {
|
|||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let messages = blocking(context.pool(), move |conn| {
|
||||
let mut messages = blocking(context.pool(), move |conn| {
|
||||
PrivateMessageQueryBuilder::create(conn, person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
|
@ -34,6 +35,17 @@ impl PerformCrud for GetPrivateMessages {
|
|||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
for pmv in messages
|
||||
.iter_mut()
|
||||
.filter(|pmv| pmv.private_message.deleted)
|
||||
{
|
||||
pmv.private_message = pmv
|
||||
.to_owned()
|
||||
.private_message
|
||||
.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
Ok(PrivateMessagesResponse {
|
||||
private_messages: messages,
|
||||
})
|
||||
|
|
|
@ -5,8 +5,11 @@ use lemmy_api_common::{
|
|||
get_local_user_view_from_jwt,
|
||||
person::{EditPrivateMessage, PrivateMessageResponse},
|
||||
};
|
||||
use lemmy_apub::ApubObjectType;
|
||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud};
|
||||
use lemmy_apub::activities::{
|
||||
private_message::create_or_update::CreateOrUpdatePrivateMessage,
|
||||
CreateOrUpdateType,
|
||||
};
|
||||
use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||
use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView};
|
||||
use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
|
||||
|
@ -44,16 +47,27 @@ impl PerformCrud for EditPrivateMessage {
|
|||
.map_err(|_| ApiError::err("couldnt_update_private_message"))?;
|
||||
|
||||
// Send the apub update
|
||||
updated_private_message
|
||||
.send_update(&local_user_view.person, context)
|
||||
CreateOrUpdatePrivateMessage::send(
|
||||
&updated_private_message,
|
||||
&local_user_view.person,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let private_message_id = data.private_message_id;
|
||||
let private_message_view = blocking(context.pool(), move |conn| {
|
||||
let mut private_message_view = blocking(context.pool(), move |conn| {
|
||||
PrivateMessageView::read(conn, private_message_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Blank out deleted or removed info
|
||||
if private_message_view.private_message.deleted {
|
||||
private_message_view.private_message = private_message_view
|
||||
.private_message
|
||||
.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
let res = PrivateMessageResponse {
|
||||
private_message_view,
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ url = { version = "2.2.2", features = ["serde"] }
|
|||
percent-encoding = "2.1.0"
|
||||
openssl = "0.10.35"
|
||||
http = "0.2.4"
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.6", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-actix = { version = "0.5.0-beta.7", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-reqwest = { version = "0.2.0", default-features = false, features = ["sha-2"] }
|
||||
base64 = "0.13.0"
|
||||
tokio = "1.8.0"
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
comment::{get_notif_recipients, send_websocket_message},
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
},
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::CreateType, base::BaseExt};
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateComment {
|
||||
to: PublicUrl,
|
||||
object: NoteExt,
|
||||
cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreateComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
// TODO: should add a check that the correct community is in cc (probably needs changes to
|
||||
// comment deserialization)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let comment = Comment::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let recipients =
|
||||
get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
|
||||
send_websocket_message(
|
||||
comment.id,
|
||||
recipients,
|
||||
UserOperationCrud::CreateComment,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
131
crates/apub/src/activities/comment/create_or_update.rs
Normal file
131
crates/apub/src/activities/comment/create_or_update.rs
Normal file
|
@ -0,0 +1,131 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
comment::{collect_non_local_mentions, get_notif_recipients, send_websocket_message},
|
||||
community::announce::AnnouncableActivities,
|
||||
extract_community,
|
||||
generate_activity_id,
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
activity_queue::send_to_community_new,
|
||||
extensions::context::lemmy_context,
|
||||
objects::{comment::Note, FromApub, ToApub},
|
||||
ActorType,
|
||||
};
|
||||
use activitystreams::link::Mention;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
values::PublicUrl,
|
||||
verify_domains_match,
|
||||
ActivityCommonFields,
|
||||
ActivityHandler,
|
||||
};
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateOrUpdateComment {
|
||||
to: PublicUrl,
|
||||
object: Note,
|
||||
cc: Vec<Url>,
|
||||
tag: Vec<Mention>,
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateOrUpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl CreateOrUpdateComment {
|
||||
pub async fn send(
|
||||
comment: &Comment,
|
||||
actor: &Person,
|
||||
kind: CreateOrUpdateType,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
// TODO: might be helpful to add a comment method to retrieve community directly
|
||||
let post_id = comment.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let id = generate_activity_id(kind.clone())?;
|
||||
let maa = collect_non_local_mentions(comment, &community, context).await?;
|
||||
|
||||
let create_or_update = CreateOrUpdateComment {
|
||||
to: PublicUrl::Public,
|
||||
object: comment.to_apub(context.pool()).await?,
|
||||
cc: maa.ccs,
|
||||
tag: maa.tags,
|
||||
kind,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: id.clone(),
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update);
|
||||
send_to_community_new(activity, &id, actor, &community, maa.inboxes, context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreateOrUpdateComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = extract_community(&self.cc, context, request_counter).await?;
|
||||
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(
|
||||
&self.common.actor,
|
||||
&community.actor_id(),
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
||||
// TODO: should add a check that the correct community is in cc (probably needs changes to
|
||||
// comment deserialization)
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let comment = Comment::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
let recipients =
|
||||
get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
|
||||
let notif_type = match self.kind {
|
||||
CreateOrUpdateType::Create => UserOperationCrud::CreateComment,
|
||||
CreateOrUpdateType::Update => UserOperationCrud::EditComment,
|
||||
};
|
||||
send_websocket_message(comment.id, recipients, notif_type, context).await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,18 +1,30 @@
|
|||
use crate::fetcher::person::get_or_fetch_and_upsert_person;
|
||||
use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs};
|
||||
use lemmy_db_queries::Crud;
|
||||
use crate::{fetcher::person::get_or_fetch_and_upsert_person, ActorType};
|
||||
use activitystreams::{
|
||||
base::BaseExt,
|
||||
link::{LinkExt, Mention},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use itertools::Itertools;
|
||||
use lemmy_api_common::{blocking, comment::CommentResponse, send_local_notifs, WebFingerResponse};
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_db_schema::{
|
||||
source::{comment::Comment, post::Post},
|
||||
source::{comment::Comment, community::Community, person::Person, post::Post},
|
||||
CommentId,
|
||||
LocalUserId,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentView;
|
||||
use lemmy_utils::{utils::scrape_text_for_mentions, LemmyError};
|
||||
use lemmy_utils::{
|
||||
request::{retry, RecvError},
|
||||
settings::structs::Settings,
|
||||
utils::{scrape_text_for_mentions, MentionData},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::{messages::SendComment, LemmyContext};
|
||||
use log::debug;
|
||||
use reqwest::Client;
|
||||
use url::Url;
|
||||
|
||||
pub mod create;
|
||||
pub mod update;
|
||||
pub mod create_or_update;
|
||||
|
||||
async fn get_notif_recipients(
|
||||
actor: &Url,
|
||||
|
@ -63,3 +75,104 @@ pub(crate) async fn send_websocket_message<
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct MentionsAndAddresses {
|
||||
pub ccs: Vec<Url>,
|
||||
pub inboxes: Vec<Url>,
|
||||
pub tags: Vec<Mention>,
|
||||
}
|
||||
|
||||
/// This takes a comment, and builds a list of to_addresses, inboxes,
|
||||
/// and mention tags, so they know where to be sent to.
|
||||
/// Addresses are the persons / addresses that go in the cc field.
|
||||
pub async fn collect_non_local_mentions(
|
||||
comment: &Comment,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<MentionsAndAddresses, LemmyError> {
|
||||
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
|
||||
let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
|
||||
// Note: dont include community inbox here, as we send to it separately with `send_to_community()`
|
||||
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
|
||||
|
||||
// Add the mention tag
|
||||
let mut tags = Vec::new();
|
||||
|
||||
// Get the person IDs for any mentions
|
||||
let mentions = scrape_text_for_mentions(&comment.content)
|
||||
.into_iter()
|
||||
// Filter only the non-local ones
|
||||
.filter(|m| !m.is_local())
|
||||
.collect::<Vec<MentionData>>();
|
||||
|
||||
for mention in &mentions {
|
||||
// TODO should it be fetching it every time?
|
||||
if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
|
||||
debug!("mention actor_id: {}", actor_id);
|
||||
addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
|
||||
|
||||
let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
|
||||
inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
|
||||
|
||||
let mut mention_tag = Mention::new();
|
||||
mention_tag.set_href(actor_id).set_name(mention.full_name());
|
||||
tags.push(mention_tag);
|
||||
}
|
||||
}
|
||||
|
||||
let inboxes = inboxes.into_iter().unique().collect();
|
||||
|
||||
Ok(MentionsAndAddresses {
|
||||
ccs: addressed_ccs,
|
||||
inboxes,
|
||||
tags,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
|
||||
/// top-level comment, the creator of the post, otherwise the creator of the parent comment.
|
||||
async fn get_comment_parent_creator(
|
||||
pool: &DbPool,
|
||||
comment: &Comment,
|
||||
) -> Result<Person, LemmyError> {
|
||||
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
|
||||
let parent_comment =
|
||||
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
||||
parent_comment.creator_id
|
||||
} else {
|
||||
let parent_post_id = comment.post_id;
|
||||
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
|
||||
parent_post.creator_id
|
||||
};
|
||||
Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
|
||||
}
|
||||
|
||||
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
|
||||
/// using webfinger.
|
||||
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
|
||||
let fetch_url = format!(
|
||||
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
|
||||
Settings::get().get_protocol_string(),
|
||||
mention.domain,
|
||||
mention.name,
|
||||
mention.domain
|
||||
);
|
||||
debug!("Fetching webfinger url: {}", &fetch_url);
|
||||
|
||||
let response = retry(|| client.get(&fetch_url).send()).await?;
|
||||
|
||||
let res: WebFingerResponse = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| RecvError(e.to_string()))?;
|
||||
|
||||
let link = res
|
||||
.links
|
||||
.iter()
|
||||
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
||||
.ok_or_else(|| anyhow!("No application/activity+json link found."))?;
|
||||
link
|
||||
.href
|
||||
.to_owned()
|
||||
.ok_or_else(|| anyhow!("No href found.").into())
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
use crate::activities::{comment::send_websocket_message, verify_mod_action};
|
||||
use activitystreams::activity::kind::RemoveType;
|
||||
use lemmy_api_common::blocking;
|
||||
use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
|
||||
use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
|
||||
use lemmy_db_queries::source::comment::Comment_;
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RemoveComment {
|
||||
to: PublicUrl,
|
||||
pub(in crate::activities::comment) object: Url,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: RemoveType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandlerNew for RemoveComment {
|
||||
async fn verify(&self, context: &LemmyContext, _: &mut i32) -> Result<(), LemmyError> {
|
||||
verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
|
||||
check_is_apub_id_valid(&self.common.actor, false)?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let comment = get_or_fetch_and_insert_comment(&self.object, context, request_counter).await?;
|
||||
|
||||
let removed_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::update_removed(conn, comment.id, true)
|
||||
})
|
||||
.await??;
|
||||
|
||||
send_websocket_message(
|
||||
removed_comment.id,
|
||||
vec![],
|
||||
UserOperationCrud::EditComment,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
use crate::activities::{
|
||||
comment::{remove::RemoveComment, send_websocket_message},
|
||||
verify_mod_action,
|
||||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_api_common::blocking;
|
||||
use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
|
||||
use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
|
||||
use lemmy_db_queries::source::comment::Comment_;
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UndoRemoveComment {
|
||||
to: PublicUrl,
|
||||
object: RemoveComment,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: UndoType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandlerNew for UndoRemoveComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_domains_match(&self.common.actor, self.common.id_unchecked())?;
|
||||
check_is_apub_id_valid(&self.common.actor, false)?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
self.object.verify(context, request_counter).await
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let comment =
|
||||
get_or_fetch_and_insert_comment(&self.object.object, context, request_counter).await?;
|
||||
|
||||
let removed_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::update_removed(conn, comment.id, false)
|
||||
})
|
||||
.await??;
|
||||
|
||||
send_websocket_message(
|
||||
removed_comment.id,
|
||||
vec![],
|
||||
UserOperationCrud::EditComment,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
comment::{get_notif_recipients, send_websocket_message},
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
},
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateComment {
|
||||
to: PublicUrl,
|
||||
object: NoteExt,
|
||||
cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
kind: UpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UpdateComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let comment = Comment::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let recipients =
|
||||
get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?;
|
||||
send_websocket_message(
|
||||
comment.id,
|
||||
recipients,
|
||||
UserOperationCrud::EditComment,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::{activity::kind::AddType, base::AnyBase};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::{source::community::CommunityModerator_, Joinable};
|
||||
use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -38,7 +38,7 @@ impl ActivityHandler for AddMod {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
comment::{create::CreateComment, update::UpdateComment},
|
||||
comment::create_or_update::CreateOrUpdateComment,
|
||||
community::{
|
||||
add_mod::AddMod,
|
||||
block_user::BlockUserFromCommunity,
|
||||
list_community_follower_inboxes,
|
||||
undo_block_user::UndoBlockUserFromCommunity,
|
||||
},
|
||||
deletion::{
|
||||
delete::DeletePostCommentOrCommunity,
|
||||
undo_delete::UndoDeletePostCommentOrCommunity,
|
||||
},
|
||||
post::{create::CreatePost, update::UpdatePost},
|
||||
generate_activity_id,
|
||||
post::create_or_update::CreateOrUpdatePost,
|
||||
removal::{
|
||||
remove::RemovePostCommentCommunityOrMod,
|
||||
undo_remove::UndoRemovePostCommentOrCommunity,
|
||||
},
|
||||
verify_activity,
|
||||
verify_community,
|
||||
voting::{
|
||||
dislike::DislikePostOrComment,
|
||||
like::LikePostOrComment,
|
||||
undo_dislike::UndoDislikePostOrComment,
|
||||
undo_like::UndoLikePostOrComment,
|
||||
},
|
||||
voting::{undo_vote::UndoVote, vote::Vote},
|
||||
},
|
||||
activity_queue::send_activity_new,
|
||||
extensions::context::lemmy_context,
|
||||
http::is_activity_already_known,
|
||||
insert_activity,
|
||||
ActorType,
|
||||
CommunityType,
|
||||
};
|
||||
use activitystreams::activity::kind::AnnounceType;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -37,14 +39,10 @@ use url::Url;
|
|||
#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)]
|
||||
#[serde(untagged)]
|
||||
pub enum AnnouncableActivities {
|
||||
CreateComment(CreateComment),
|
||||
UpdateComment(UpdateComment),
|
||||
CreatePost(CreatePost),
|
||||
UpdatePost(UpdatePost),
|
||||
LikePostOrComment(LikePostOrComment),
|
||||
DislikePostOrComment(DislikePostOrComment),
|
||||
UndoLikePostOrComment(UndoLikePostOrComment),
|
||||
UndoDislikePostOrComment(UndoDislikePostOrComment),
|
||||
CreateOrUpdateComment(CreateOrUpdateComment),
|
||||
CreateOrUpdatePost(Box<CreateOrUpdatePost>),
|
||||
Vote(Vote),
|
||||
UndoVote(UndoVote),
|
||||
DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
|
||||
UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
|
||||
RemovePostCommentCommunityOrMod(RemovePostCommentCommunityOrMod),
|
||||
|
@ -66,6 +64,38 @@ pub struct AnnounceActivity {
|
|||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl AnnounceActivity {
|
||||
pub async fn send(
|
||||
object: AnnouncableActivities,
|
||||
community: &Community,
|
||||
additional_inboxes: Vec<Url>,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let announce = AnnounceActivity {
|
||||
to: PublicUrl::Public,
|
||||
object,
|
||||
cc: vec![community.followers_url()],
|
||||
kind: AnnounceType::Announce,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: generate_activity_id(&AnnounceType::Announce)?,
|
||||
actor: community.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
let inboxes = list_community_follower_inboxes(community, additional_inboxes, context).await?;
|
||||
send_activity_new(
|
||||
context,
|
||||
&announce,
|
||||
&announce.common.id,
|
||||
community,
|
||||
inboxes,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for AnnounceActivity {
|
||||
async fn verify(
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::activity::kind::BlockType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::{Bannable, Followable};
|
||||
use lemmy_db_schema::source::community::{
|
||||
CommunityFollower,
|
||||
|
@ -36,7 +36,7 @@ impl ActivityHandler for BlockUserFromCommunity {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use crate::{check_is_apub_id_valid, CommunityType};
|
||||
use itertools::Itertools;
|
||||
use lemmy_api_common::{blocking, community::CommunityResponse};
|
||||
use lemmy_db_schema::CommunityId;
|
||||
use lemmy_db_schema::{source::community::Community, CommunityId};
|
||||
use lemmy_db_views_actor::community_view::CommunityView;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext};
|
||||
use url::Url;
|
||||
|
||||
pub mod add_mod;
|
||||
pub mod announce;
|
||||
|
@ -33,3 +36,23 @@ pub(crate) async fn send_websocket_message<
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_community_follower_inboxes(
|
||||
community: &Community,
|
||||
additional_inboxes: Vec<Url>,
|
||||
context: &LemmyContext,
|
||||
) -> Result<Vec<Url>, LemmyError> {
|
||||
Ok(
|
||||
vec![
|
||||
community.get_follower_inboxes(context.pool()).await?,
|
||||
additional_inboxes,
|
||||
]
|
||||
.iter()
|
||||
.flatten()
|
||||
.unique()
|
||||
.filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname))
|
||||
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
||||
.map(|inbox| inbox.to_owned())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::Bannable;
|
||||
use lemmy_db_schema::source::community::{CommunityPersonBan, CommunityPersonBanForm};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -36,7 +36,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::activity::kind::UpdateType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::{ApubObject, Crud};
|
||||
use lemmy_db_schema::source::community::{Community, CommunityForm};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -39,7 +39,7 @@ impl ActivityHandler for UpdateCommunity {
|
|||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::activity::kind::DeleteType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::{
|
||||
source::{comment::Comment_, community::Community_, post::Post_},
|
||||
Crud,
|
||||
|
@ -64,7 +64,8 @@ impl ActivityHandler for DeletePostCommentOrCommunity {
|
|||
}
|
||||
// deleting a post or comment
|
||||
else {
|
||||
verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
|
||||
.await?;
|
||||
let object_creator =
|
||||
get_post_or_comment_actor_id(&self.object, context, request_counter).await?;
|
||||
verify_urls_match(&self.common.actor, &object_creator)?;
|
||||
|
@ -83,7 +84,7 @@ impl ActivityHandler for DeletePostCommentOrCommunity {
|
|||
if let Ok(community) = object_community {
|
||||
if community.local {
|
||||
// repeat these checks just to be sure
|
||||
verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
|
||||
verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
|
||||
.await?;
|
||||
verify_mod_action(&self.common.actor, self.object.clone(), context).await?;
|
||||
let mod_ =
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -54,7 +54,8 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity {
|
|||
}
|
||||
// restoring a post or comment
|
||||
else {
|
||||
verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
|
||||
.await?;
|
||||
verify_urls_match(&self.common.actor, &self.object.common().actor)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -71,7 +72,7 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity {
|
|||
if let Ok(community) = object_community {
|
||||
if community.local {
|
||||
// repeat these checks just to be sure
|
||||
verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
|
||||
verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
|
||||
.await?;
|
||||
verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?;
|
||||
let mod_ =
|
||||
|
|
|
@ -13,9 +13,12 @@ use lemmy_db_schema::{
|
|||
DbUrl,
|
||||
};
|
||||
use lemmy_db_views_actor::community_view::CommunityView;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::ToString;
|
||||
use url::{ParseError, Url};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod comment;
|
||||
pub mod community;
|
||||
|
@ -27,6 +30,12 @@ pub mod removal;
|
|||
pub mod send;
|
||||
pub mod voting;
|
||||
|
||||
#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
|
||||
pub enum CreateOrUpdateType {
|
||||
Create,
|
||||
Update,
|
||||
}
|
||||
|
||||
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
|
||||
/// doesn't have a site ban.
|
||||
async fn verify_person(
|
||||
|
@ -41,27 +50,34 @@ async fn verify_person(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetches the person and community to verify their type, then checks if person is banned from site
|
||||
/// or community.
|
||||
async fn verify_person_in_community(
|
||||
person_id: &Url,
|
||||
pub(crate) async fn extract_community(
|
||||
cc: &[Url],
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<Community, LemmyError> {
|
||||
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
|
||||
let mut cc_iter = cc.iter();
|
||||
let community: Community = loop {
|
||||
loop {
|
||||
if let Some(cid) = cc_iter.next() {
|
||||
if let Ok(c) = get_or_fetch_and_upsert_community(cid, context, request_counter).await {
|
||||
break c;
|
||||
break Ok(c);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!("No community found in cc").into());
|
||||
}
|
||||
};
|
||||
check_community_or_site_ban(&person, community.id, context.pool()).await?;
|
||||
Ok(community)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the person and community to verify their type, then checks if person is banned from site
|
||||
/// or community.
|
||||
pub(crate) async fn verify_person_in_community(
|
||||
person_id: &Url,
|
||||
community_id: &Url,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
|
||||
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
|
||||
check_community_or_site_ban(&person, community.id, context.pool()).await
|
||||
}
|
||||
|
||||
/// Simply check that the url actually refers to a valid group.
|
||||
|
@ -80,13 +96,16 @@ fn verify_activity(common: &ActivityCommonFields) -> Result<(), LemmyError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn verify_mod_action(
|
||||
/// Verify that the actor is a community mod. This check is only run if the community is local,
|
||||
/// because in case of remote communities, admins can also perform mod actions. As admin status
|
||||
/// is not federated, we cant verify their actions remotely.
|
||||
pub(crate) async fn verify_mod_action(
|
||||
actor_id: &Url,
|
||||
activity_cc: Url,
|
||||
community: Url,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read_from_apub_id(conn, &activity_cc.into())
|
||||
Community::read_from_apub_id(conn, &community.into())
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
@ -120,3 +139,18 @@ fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<()
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a unique ID for an activity, in the format:
|
||||
/// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
|
||||
fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let id = format!(
|
||||
"{}/activities/{}/{}",
|
||||
Settings::get().get_protocol_and_hostname(),
|
||||
kind.to_string().to_lowercase(),
|
||||
Uuid::new_v4()
|
||||
);
|
||||
Url::parse(&id)
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
use crate::{
|
||||
activities::{post::send_websocket_message, verify_activity, verify_person_in_community},
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::FromApub,
|
||||
ActorType,
|
||||
PageExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::CreateType, base::BaseExt};
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_db_schema::source::post::Post;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatePost {
|
||||
to: PublicUrl,
|
||||
object: PageExt,
|
||||
cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreatePost {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor =
|
||||
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
||||
let post = Post::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
actor.actor_id(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_websocket_message(post.id, UserOperationCrud::CreatePost, context).await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
142
crates/apub/src/activities/post/create_or_update.rs
Normal file
142
crates/apub/src/activities/post/create_or_update.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::announce::AnnouncableActivities,
|
||||
extract_community,
|
||||
generate_activity_id,
|
||||
post::send_websocket_message,
|
||||
verify_activity,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
activity_queue::send_to_community_new,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::{post::Page, FromApub, ToApub},
|
||||
ActorType,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
values::PublicUrl,
|
||||
verify_domains_match,
|
||||
verify_urls_match,
|
||||
ActivityCommonFields,
|
||||
ActivityHandler,
|
||||
};
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateOrUpdatePost {
|
||||
to: PublicUrl,
|
||||
object: Page,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateOrUpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl CreateOrUpdatePost {
|
||||
pub async fn send(
|
||||
post: &Post,
|
||||
actor: &Person,
|
||||
kind: CreateOrUpdateType,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let id = generate_activity_id(kind.clone())?;
|
||||
let create_or_update = CreateOrUpdatePost {
|
||||
to: PublicUrl::Public,
|
||||
object: post.to_apub(context.pool()).await?,
|
||||
cc: [community.actor_id()],
|
||||
kind,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: id.clone(),
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
let activity = AnnouncableActivities::CreateOrUpdatePost(Box::new(create_or_update));
|
||||
send_to_community_new(activity, &id, actor, &community, vec![], context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreateOrUpdatePost {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
let community = extract_community(&self.cc, context, request_counter).await?;
|
||||
let community_id = community.actor_id();
|
||||
verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
|
||||
match self.kind {
|
||||
CreateOrUpdateType::Create => {
|
||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
||||
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
||||
// Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
|
||||
// However, when fetching a remote post we generate a new create activity with the current
|
||||
// locked/stickied value, so this check may fail. So only check if its a local community,
|
||||
// because then we will definitely receive all create and update activities separately.
|
||||
let is_stickied_or_locked =
|
||||
self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
|
||||
if community.local && is_stickied_or_locked {
|
||||
return Err(anyhow!("New post cannot be stickied or locked").into());
|
||||
}
|
||||
}
|
||||
CreateOrUpdateType::Update => {
|
||||
let is_mod_action = self.object.is_mod_action(context.pool()).await?;
|
||||
if is_mod_action {
|
||||
verify_mod_action(&self.common.actor, community_id, context).await?;
|
||||
} else {
|
||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
||||
verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor =
|
||||
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
||||
let post = Post::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
actor.actor_id(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let notif_type = match self.kind {
|
||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePost,
|
||||
CreateOrUpdateType::Update => UserOperationCrud::EditPost,
|
||||
};
|
||||
send_websocket_message(post.id, notif_type, context).await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -4,8 +4,7 @@ use lemmy_db_views::post_view::PostView;
|
|||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{messages::SendPost, LemmyContext};
|
||||
|
||||
pub mod create;
|
||||
pub mod update;
|
||||
pub mod create_or_update;
|
||||
|
||||
pub(crate) async fn send_websocket_message<
|
||||
OP: ToString + Send + lemmy_websocket::OperationType + 'static,
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
post::send_websocket_message,
|
||||
verify_activity,
|
||||
verify_mod_action,
|
||||
verify_person_in_community,
|
||||
},
|
||||
objects::{FromApub, FromApubToForm},
|
||||
ActorType,
|
||||
PageExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
|
||||
use anyhow::Context;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_db_queries::ApubObject;
|
||||
use lemmy_db_schema::{
|
||||
source::post::{Post, PostForm},
|
||||
DbUrl,
|
||||
};
|
||||
use lemmy_utils::{location_info, LemmyError};
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdatePost {
|
||||
to: PublicUrl,
|
||||
object: PageExt,
|
||||
cc: Vec<Url>,
|
||||
#[serde(rename = "type")]
|
||||
kind: UpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UpdatePost {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
let community =
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
|
||||
let temp_post = PostForm::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let post_id: DbUrl = temp_post.ap_id.context(location_info!())?;
|
||||
let old_post = blocking(context.pool(), move |conn| {
|
||||
Post::read_from_apub_id(conn, &post_id)
|
||||
})
|
||||
.await??;
|
||||
let stickied = temp_post.stickied.context(location_info!())?;
|
||||
let locked = temp_post.locked.context(location_info!())?;
|
||||
// community mod changed locked/sticky status
|
||||
if (stickied != old_post.stickied) || (locked != old_post.locked) {
|
||||
verify_mod_action(&self.common.actor, community.actor_id(), context).await?;
|
||||
}
|
||||
// user edited their own post
|
||||
else {
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post = Post::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
// TODO: we already check here if the mod action is valid, can remove that check param
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_websocket_message(post.id, UserOperationCrud::EditPost, context).await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
use crate::{
|
||||
activities::{private_message::send_websocket_message, verify_activity, verify_person},
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::CreateType, base::BaseExt};
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatePrivateMessage {
|
||||
to: Url,
|
||||
object: NoteExt,
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreatePrivateMessage {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person(&self.common.actor, context, request_counter).await?;
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let private_message = PrivateMessage::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_websocket_message(
|
||||
private_message.id,
|
||||
UserOperationCrud::CreatePrivateMessage,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
107
crates/apub/src/activities/private_message/create_or_update.rs
Normal file
107
crates/apub/src/activities/private_message/create_or_update.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
generate_activity_id,
|
||||
private_message::send_websocket_message,
|
||||
verify_activity,
|
||||
verify_person,
|
||||
CreateOrUpdateType,
|
||||
},
|
||||
activity_queue::send_activity_new,
|
||||
extensions::context::lemmy_context,
|
||||
objects::{private_message::Note, FromApub, ToApub},
|
||||
ActorType,
|
||||
};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateOrUpdatePrivateMessage {
|
||||
to: Url,
|
||||
object: Note,
|
||||
#[serde(rename = "type")]
|
||||
kind: CreateOrUpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl CreateOrUpdatePrivateMessage {
|
||||
pub async fn send(
|
||||
private_message: &PrivateMessage,
|
||||
actor: &Person,
|
||||
kind: CreateOrUpdateType,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let recipient_id = private_message.recipient_id;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let id = generate_activity_id(kind.clone())?;
|
||||
let create_or_update = CreateOrUpdatePrivateMessage {
|
||||
to: recipient.actor_id(),
|
||||
object: private_message.to_apub(context.pool()).await?,
|
||||
kind,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: id.clone(),
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
send_activity_new(
|
||||
context,
|
||||
&create_or_update,
|
||||
&id,
|
||||
actor,
|
||||
vec![recipient.get_shared_inbox_or_inbox_url()],
|
||||
true,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for CreateOrUpdatePrivateMessage {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person(&self.common.actor, context, request_counter).await?;
|
||||
verify_domains_match(&self.common.actor, &self.object.id)?;
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let private_message = PrivateMessage::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let notif_type = match self.kind {
|
||||
CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage,
|
||||
CreateOrUpdateType::Update => UserOperationCrud::EditPrivateMessage,
|
||||
};
|
||||
send_websocket_message(private_message.id, notif_type, context).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -4,10 +4,9 @@ use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::Priva
|
|||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperationCrud};
|
||||
|
||||
pub mod create;
|
||||
pub mod create_or_update;
|
||||
pub mod delete;
|
||||
pub mod undo_delete;
|
||||
pub mod update;
|
||||
|
||||
async fn send_websocket_message(
|
||||
private_message_id: PrivateMessageId,
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
use crate::{
|
||||
activities::{private_message::send_websocket_message, verify_activity, verify_person},
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
};
|
||||
use activitystreams::{activity::kind::UpdateType, base::BaseExt};
|
||||
use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_schema::source::private_message::PrivateMessage;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperationCrud};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdatePrivateMessage {
|
||||
to: Url,
|
||||
object: NoteExt,
|
||||
#[serde(rename = "type")]
|
||||
kind: UpdateType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UpdatePrivateMessage {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person(&self.common.actor, context, request_counter).await?;
|
||||
verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let private_message = PrivateMessage::from_apub(
|
||||
&self.object,
|
||||
context,
|
||||
self.common.actor.clone(),
|
||||
request_counter,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
send_websocket_message(
|
||||
private_message.id,
|
||||
UserOperationCrud::EditPrivateMessage,
|
||||
context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ use crate::{
|
|||
use activitystreams::{activity::kind::RemoveType, base::AnyBase};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::{
|
||||
source::{comment::Comment_, community::Community_, post::Post_},
|
||||
Joinable,
|
||||
|
@ -64,13 +64,13 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod {
|
|||
}
|
||||
// removing community mod
|
||||
else if let Some(target) = &self.target {
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
verify_add_remove_moderator_target(target, self.cc[0].clone())?;
|
||||
}
|
||||
// removing a post or comment
|
||||
else {
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
use activitystreams::activity::kind::UndoType;
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -52,7 +52,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity {
|
|||
}
|
||||
// removing a post or comment
|
||||
else {
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
|
||||
}
|
||||
self.object.verify(context, request_counter).await?;
|
||||
|
|
|
@ -1,114 +1,28 @@
|
|||
use crate::{
|
||||
activities::send::generate_activity_id,
|
||||
activity_queue::{send_comment_mentions, send_to_community},
|
||||
activities::generate_activity_id,
|
||||
activity_queue::send_to_community,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::ToApub,
|
||||
ActorType,
|
||||
ApubLikeableType,
|
||||
ApubObjectType,
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::{
|
||||
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
||||
Create,
|
||||
kind::{DeleteType, RemoveType, UndoType},
|
||||
Delete,
|
||||
Dislike,
|
||||
Like,
|
||||
Remove,
|
||||
Undo,
|
||||
Update,
|
||||
},
|
||||
base::AnyBase,
|
||||
link::Mention,
|
||||
prelude::*,
|
||||
public,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use itertools::Itertools;
|
||||
use lemmy_api_common::{blocking, WebFingerResponse};
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
|
||||
use lemmy_utils::{
|
||||
request::{retry, RecvError},
|
||||
settings::structs::Settings,
|
||||
utils::{scrape_text_for_mentions, MentionData},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
use reqwest::Client;
|
||||
use serde_json::Error;
|
||||
use url::Url;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObjectType for Comment {
|
||||
/// Send out information about a newly created comment, to the followers of the community and
|
||||
/// mentioned persons.
|
||||
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let maa = collect_non_local_mentions(self, &community, context).await?;
|
||||
|
||||
let mut create = Create::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
note.into_any_base()?,
|
||||
);
|
||||
create
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(CreateType::Create)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(maa.ccs.to_owned())
|
||||
// Set the mention tags
|
||||
.set_many_tags(maa.get_tags()?);
|
||||
|
||||
send_to_community(create.clone(), creator, &community, None, context).await?;
|
||||
send_comment_mentions(creator, maa.inboxes, create, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community and mentioned
|
||||
/// persons.
|
||||
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let maa = collect_non_local_mentions(self, &community, context).await?;
|
||||
|
||||
let mut update = Update::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
note.into_any_base()?,
|
||||
);
|
||||
update
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UpdateType::Update)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(maa.ccs.to_owned())
|
||||
// Set the mention tags
|
||||
.set_many_tags(maa.get_tags()?);
|
||||
|
||||
send_to_community(update.clone(), creator, &community, None, context).await?;
|
||||
send_comment_mentions(creator, maa.inboxes, update, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
@ -124,7 +38,7 @@ impl ApubObjectType for Comment {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -153,7 +67,7 @@ impl ApubObjectType for Comment {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -164,7 +78,7 @@ impl ApubObjectType for Comment {
|
|||
delete.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -188,7 +102,7 @@ impl ApubObjectType for Comment {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -217,7 +131,7 @@ impl ApubObjectType for Comment {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -228,7 +142,7 @@ impl ApubObjectType for Comment {
|
|||
remove.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -237,204 +151,3 @@ impl ApubObjectType for Comment {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubLikeableType for Comment {
|
||||
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(like, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut dislike = Dislike::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
dislike
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(dislike, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
like.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(undo, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct MentionsAndAddresses {
|
||||
ccs: Vec<Url>,
|
||||
inboxes: Vec<Url>,
|
||||
tags: Vec<Mention>,
|
||||
}
|
||||
|
||||
impl MentionsAndAddresses {
|
||||
fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
|
||||
self
|
||||
.tags
|
||||
.iter()
|
||||
.map(|t| t.to_owned().into_any_base())
|
||||
.collect::<Result<Vec<AnyBase>, Error>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// This takes a comment, and builds a list of to_addresses, inboxes,
|
||||
/// and mention tags, so they know where to be sent to.
|
||||
/// Addresses are the persons / addresses that go in the cc field.
|
||||
async fn collect_non_local_mentions(
|
||||
comment: &Comment,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> Result<MentionsAndAddresses, LemmyError> {
|
||||
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
|
||||
let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
|
||||
// Note: dont include community inbox here, as we send to it separately with `send_to_community()`
|
||||
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
|
||||
|
||||
// Add the mention tag
|
||||
let mut tags = Vec::new();
|
||||
|
||||
// Get the person IDs for any mentions
|
||||
let mentions = scrape_text_for_mentions(&comment.content)
|
||||
.into_iter()
|
||||
// Filter only the non-local ones
|
||||
.filter(|m| !m.is_local())
|
||||
.collect::<Vec<MentionData>>();
|
||||
|
||||
for mention in &mentions {
|
||||
// TODO should it be fetching it every time?
|
||||
if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
|
||||
debug!("mention actor_id: {}", actor_id);
|
||||
addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
|
||||
|
||||
let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
|
||||
inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
|
||||
|
||||
let mut mention_tag = Mention::new();
|
||||
mention_tag.set_href(actor_id).set_name(mention.full_name());
|
||||
tags.push(mention_tag);
|
||||
}
|
||||
}
|
||||
|
||||
let inboxes = inboxes.into_iter().unique().collect();
|
||||
|
||||
Ok(MentionsAndAddresses {
|
||||
ccs: addressed_ccs,
|
||||
inboxes,
|
||||
tags,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
|
||||
/// top-level comment, the creator of the post, otherwise the creator of the parent comment.
|
||||
async fn get_comment_parent_creator(
|
||||
pool: &DbPool,
|
||||
comment: &Comment,
|
||||
) -> Result<Person, LemmyError> {
|
||||
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
|
||||
let parent_comment =
|
||||
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
||||
parent_comment.creator_id
|
||||
} else {
|
||||
let parent_post_id = comment.post_id;
|
||||
let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
|
||||
parent_post.creator_id
|
||||
};
|
||||
Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
|
||||
}
|
||||
|
||||
/// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
|
||||
/// using webfinger.
|
||||
async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
|
||||
let fetch_url = format!(
|
||||
"{}://{}/.well-known/webfinger?resource=acct:{}@{}",
|
||||
Settings::get().get_protocol_string(),
|
||||
mention.domain,
|
||||
mention.name,
|
||||
mention.domain
|
||||
);
|
||||
debug!("Fetching webfinger url: {}", &fetch_url);
|
||||
|
||||
let response = retry(|| client.get(&fetch_url).send()).await?;
|
||||
|
||||
let res: WebFingerResponse = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| RecvError(e.to_string()))?;
|
||||
|
||||
let link = res
|
||||
.links
|
||||
.iter()
|
||||
.find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
|
||||
.ok_or_else(|| anyhow!("No application/activity+json link found."))?;
|
||||
link
|
||||
.href
|
||||
.to_owned()
|
||||
.ok_or_else(|| anyhow!("No href found.").into())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
activities::send::generate_activity_id,
|
||||
activities::generate_activity_id,
|
||||
activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
|
||||
check_is_apub_id_valid,
|
||||
extensions::context::lemmy_context,
|
||||
|
@ -54,7 +54,7 @@ impl ActorType for Community {
|
|||
self.local
|
||||
}
|
||||
fn actor_id(&self) -> Url {
|
||||
self.actor_id.to_owned().into_inner()
|
||||
self.actor_id.to_owned().into()
|
||||
}
|
||||
fn name(&self) -> String {
|
||||
self.name.clone()
|
||||
|
@ -78,7 +78,7 @@ impl ActorType for Community {
|
|||
#[async_trait::async_trait(?Send)]
|
||||
impl CommunityType for Community {
|
||||
fn followers_url(&self) -> Url {
|
||||
self.followers_url.clone().into_inner()
|
||||
self.followers_url.clone().into()
|
||||
}
|
||||
|
||||
/// As a local community, accept the follow request from a remote person.
|
||||
|
@ -98,7 +98,7 @@ impl CommunityType for Community {
|
|||
follow.into_any_base()?,
|
||||
);
|
||||
accept
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(AcceptType::Accept)?)
|
||||
.set_to(person.actor_id());
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl CommunityType for Community {
|
|||
self.to_apub(context.pool()).await?.into_any_base()?,
|
||||
);
|
||||
update
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UpdateType::Update)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
@ -134,7 +134,7 @@ impl CommunityType for Community {
|
|||
if self.local {
|
||||
let mut delete = Delete::new(self.actor_id(), self.actor_id());
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
@ -145,7 +145,7 @@ impl CommunityType for Community {
|
|||
else {
|
||||
let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
@ -163,14 +163,14 @@ impl CommunityType for Community {
|
|||
if self.local {
|
||||
let mut delete = Delete::new(self.actor_id(), self.actor_id());
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
||||
let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
@ -181,14 +181,14 @@ impl CommunityType for Community {
|
|||
else {
|
||||
let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
||||
let mut undo = Undo::new(mod_.actor_id(), delete.into_any_base()?);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
@ -202,7 +202,7 @@ impl CommunityType for Community {
|
|||
async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let mut remove = Remove::new(self.actor_id(), self.actor_id());
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
@ -215,7 +215,7 @@ impl CommunityType for Community {
|
|||
async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let mut remove = Remove::new(self.actor_id(), self.actor_id());
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
@ -223,7 +223,7 @@ impl CommunityType for Community {
|
|||
// Undo that fake activity
|
||||
let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.followers_url()]);
|
||||
|
@ -267,7 +267,7 @@ impl CommunityType for Community {
|
|||
}
|
||||
let mut announce = Announce::new(self.actor_id(), activity);
|
||||
announce
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(AnnounceType::Announce)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(ccs);
|
||||
|
@ -306,7 +306,7 @@ impl CommunityType for Community {
|
|||
) -> Result<(), LemmyError> {
|
||||
let mut add = Add::new(actor.actor_id(), added_mod.actor_id());
|
||||
add
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(AddType::Add)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()])
|
||||
|
@ -324,7 +324,7 @@ impl CommunityType for Community {
|
|||
) -> Result<(), LemmyError> {
|
||||
let mut remove = Remove::new(actor.actor_id(), removed_mod.actor_id());
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()])
|
||||
|
@ -342,7 +342,7 @@ impl CommunityType for Community {
|
|||
) -> Result<(), LemmyError> {
|
||||
let mut block = Block::new(actor.actor_id(), blocked_user.actor_id());
|
||||
block
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(BlockType::Block)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
@ -359,7 +359,7 @@ impl CommunityType for Community {
|
|||
) -> Result<(), LemmyError> {
|
||||
let mut block = Block::new(actor.actor_id(), unblocked_user.actor_id());
|
||||
block
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(BlockType::Block)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
@ -367,7 +367,7 @@ impl CommunityType for Community {
|
|||
// Undo that fake activity
|
||||
let mut undo = Undo::new(actor.actor_id(), block.into_any_base()?);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![self.actor_id()]);
|
||||
|
|
|
@ -1,24 +1,5 @@
|
|||
use lemmy_utils::settings::structs::Settings;
|
||||
use url::{ParseError, Url};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) mod comment;
|
||||
pub(crate) mod community;
|
||||
pub(crate) mod person;
|
||||
pub(crate) mod post;
|
||||
pub(crate) mod private_message;
|
||||
|
||||
/// Generate a unique ID for an activity, in the format:
|
||||
/// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36`
|
||||
fn generate_activity_id<T>(kind: T) -> Result<Url, ParseError>
|
||||
where
|
||||
T: ToString,
|
||||
{
|
||||
let id = format!(
|
||||
"{}/activities/{}/{}",
|
||||
Settings::get().get_protocol_and_hostname(),
|
||||
kind.to_string().to_lowercase(),
|
||||
Uuid::new_v4()
|
||||
);
|
||||
Url::parse(&id)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
activities::send::generate_activity_id,
|
||||
activities::generate_activity_id,
|
||||
activity_queue::send_activity_single_dest,
|
||||
extensions::context::lemmy_context,
|
||||
ActorType,
|
||||
|
@ -78,7 +78,7 @@ impl UserType for Person {
|
|||
|
||||
let mut follow = Follow::new(self.actor_id(), community.actor_id());
|
||||
follow
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
||||
.set_to(community.actor_id());
|
||||
|
||||
|
@ -99,7 +99,7 @@ impl UserType for Person {
|
|||
|
||||
let mut follow = Follow::new(self.actor_id(), community.actor_id());
|
||||
follow
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(FollowType::Follow)?)
|
||||
.set_to(community.actor_id());
|
||||
|
||||
|
@ -109,7 +109,7 @@ impl UserType for Person {
|
|||
follow.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(community.actor_id());
|
||||
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
use crate::{
|
||||
activities::send::generate_activity_id,
|
||||
activities::generate_activity_id,
|
||||
activity_queue::send_to_community,
|
||||
extensions::context::lemmy_context,
|
||||
objects::ToApub,
|
||||
ActorType,
|
||||
ApubLikeableType,
|
||||
ApubObjectType,
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::{
|
||||
kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
|
||||
Create,
|
||||
kind::{DeleteType, RemoveType, UndoType},
|
||||
Delete,
|
||||
Dislike,
|
||||
Like,
|
||||
Remove,
|
||||
Undo,
|
||||
Update,
|
||||
},
|
||||
prelude::*,
|
||||
public,
|
||||
|
@ -29,54 +23,6 @@ use lemmy_websocket::LemmyContext;
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObjectType for Post {
|
||||
/// Send out information about a newly created post, to the followers of the community.
|
||||
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let page = self.to_apub(context.pool()).await?;
|
||||
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut create = Create::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
page.into_any_base()?,
|
||||
);
|
||||
create
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(CreateType::Create)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(create, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let page = self.to_apub(context.pool()).await?;
|
||||
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut update = Update::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
page.into_any_base()?,
|
||||
);
|
||||
update
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UpdateType::Update)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(update, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
|
@ -89,7 +35,7 @@ impl ApubObjectType for Post {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -114,7 +60,7 @@ impl ApubObjectType for Post {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -125,7 +71,7 @@ impl ApubObjectType for Post {
|
|||
delete.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -146,7 +92,7 @@ impl ApubObjectType for Post {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -171,7 +117,7 @@ impl ApubObjectType for Post {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
remove
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(RemoveType::Remove)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -182,7 +128,7 @@ impl ApubObjectType for Post {
|
|||
remove.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
@ -191,84 +137,3 @@ impl ApubObjectType for Post {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubLikeableType for Post {
|
||||
async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(like, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut dislike = Dislike::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
dislike
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(DislikeType::Dislike)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(dislike, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &Person,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut like = Like::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
like
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(LikeType::Like)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
// Undo that fake activity
|
||||
let mut undo = Undo::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
like.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(public())
|
||||
.set_many_ccs(vec![community.actor_id()]);
|
||||
|
||||
send_to_community(undo, creator, &community, None, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
use crate::{
|
||||
activities::send::generate_activity_id,
|
||||
activities::generate_activity_id,
|
||||
activity_queue::send_activity_single_dest,
|
||||
extensions::context::lemmy_context,
|
||||
objects::ToApub,
|
||||
ActorType,
|
||||
ApubObjectType,
|
||||
};
|
||||
use activitystreams::{
|
||||
activity::{
|
||||
kind::{CreateType, DeleteType, UndoType, UpdateType},
|
||||
Create,
|
||||
kind::{DeleteType, UndoType},
|
||||
Delete,
|
||||
Undo,
|
||||
Update,
|
||||
},
|
||||
prelude::*,
|
||||
base::{BaseExt, ExtendsExt},
|
||||
object::ObjectExt,
|
||||
};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::Crud;
|
||||
|
@ -24,49 +22,6 @@ use lemmy_websocket::LemmyContext;
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ApubObjectType for PrivateMessage {
|
||||
/// Send out information about a newly created private message
|
||||
async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut create = Create::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
note.into_any_base()?,
|
||||
);
|
||||
|
||||
create
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(CreateType::Create)?)
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(create, creator, recipient.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited private message, to the followers of the community.
|
||||
async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let note = self.to_apub(context.pool()).await?;
|
||||
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient =
|
||||
blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
let mut update = Update::new(
|
||||
creator.actor_id.to_owned().into_inner(),
|
||||
note.into_any_base()?,
|
||||
);
|
||||
update
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(generate_activity_id(UpdateType::Update)?)
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient =
|
||||
|
@ -77,7 +32,7 @@ impl ApubObjectType for PrivateMessage {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
|
@ -99,7 +54,7 @@ impl ApubObjectType for PrivateMessage {
|
|||
self.ap_id.to_owned().into_inner(),
|
||||
);
|
||||
delete
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(DeleteType::Delete)?)
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
|
@ -109,7 +64,7 @@ impl ApubObjectType for PrivateMessage {
|
|||
delete.into_any_base()?,
|
||||
);
|
||||
undo
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(generate_activity_id(UndoType::Undo)?)
|
||||
.set_to(recipient.actor_id());
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
use crate::activities::{
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::receive_like_or_dislike,
|
||||
};
|
||||
use activitystreams::activity::kind::DislikeType;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DislikePostOrComment {
|
||||
to: PublicUrl,
|
||||
pub(in crate::activities) object: Url,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: DislikeType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for DislikePostOrComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
receive_like_or_dislike(
|
||||
-1,
|
||||
&self.common.actor,
|
||||
&self.object,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
use crate::activities::{
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::receive_like_or_dislike,
|
||||
};
|
||||
use activitystreams::activity::kind::LikeType;
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LikePostOrComment {
|
||||
to: PublicUrl,
|
||||
pub(in crate::activities::voting) object: Url,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: LikeType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for LikePostOrComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
receive_like_or_dislike(
|
||||
1,
|
||||
&self.common.actor,
|
||||
&self.object,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,62 +1,33 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
use crate::activities::{
|
||||
comment::send_websocket_message as send_comment_message,
|
||||
post::send_websocket_message as send_post_message,
|
||||
},
|
||||
fetcher::{
|
||||
objects::get_or_fetch_and_insert_post_or_comment,
|
||||
person::get_or_fetch_and_upsert_person,
|
||||
},
|
||||
PostOrComment,
|
||||
voting::vote::VoteType,
|
||||
};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::Likeable;
|
||||
use lemmy_db_schema::source::{
|
||||
comment::{Comment, CommentLike, CommentLikeForm},
|
||||
person::Person,
|
||||
post::{Post, PostLike, PostLikeForm},
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{LemmyContext, UserOperation};
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
pub mod dislike;
|
||||
pub mod like;
|
||||
pub mod undo_dislike;
|
||||
pub mod undo_like;
|
||||
pub mod undo_vote;
|
||||
pub mod vote;
|
||||
|
||||
pub(in crate::activities::voting) async fn receive_like_or_dislike(
|
||||
score: i16,
|
||||
actor: &Url,
|
||||
object: &Url,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
|
||||
PostOrComment::Post(p) => {
|
||||
like_or_dislike_post(score, actor, p.deref(), context, request_counter).await
|
||||
}
|
||||
PostOrComment::Comment(c) => {
|
||||
like_or_dislike_comment(score, actor, c.deref(), context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn like_or_dislike_comment(
|
||||
score: i16,
|
||||
actor: &Url,
|
||||
async fn vote_comment(
|
||||
vote_type: &VoteType,
|
||||
actor: Person,
|
||||
comment: &Comment,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let like_form = CommentLikeForm {
|
||||
comment_id,
|
||||
post_id: comment.post_id,
|
||||
person_id: actor.id,
|
||||
score,
|
||||
score: vote_type.into(),
|
||||
};
|
||||
let person_id = actor.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
|
@ -74,20 +45,17 @@ async fn like_or_dislike_comment(
|
|||
.await
|
||||
}
|
||||
|
||||
async fn like_or_dislike_post(
|
||||
score: i16,
|
||||
actor: &Url,
|
||||
async fn vote_post(
|
||||
vote_type: &VoteType,
|
||||
actor: Person,
|
||||
post: &Post,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let like_form = PostLikeForm {
|
||||
post_id: post.id,
|
||||
person_id: actor.id,
|
||||
score,
|
||||
score: vote_type.into(),
|
||||
};
|
||||
let person_id = actor.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
|
@ -99,30 +67,11 @@ async fn like_or_dislike_post(
|
|||
send_post_message(post.id, UserOperation::CreatePostLike, context).await
|
||||
}
|
||||
|
||||
pub(in crate::activities::voting) async fn receive_undo_like_or_dislike(
|
||||
actor: &Url,
|
||||
object: &Url,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
match get_or_fetch_and_insert_post_or_comment(object, context, request_counter).await? {
|
||||
PostOrComment::Post(p) => {
|
||||
undo_like_or_dislike_post(actor, p.deref(), context, request_counter).await
|
||||
}
|
||||
PostOrComment::Comment(c) => {
|
||||
undo_like_or_dislike_comment(actor, c.deref(), context, request_counter).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn undo_like_or_dislike_comment(
|
||||
actor: &Url,
|
||||
async fn undo_vote_comment(
|
||||
actor: Person,
|
||||
comment: &Comment,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
|
||||
|
||||
let comment_id = comment.id;
|
||||
let person_id = actor.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
|
@ -139,14 +88,11 @@ async fn undo_like_or_dislike_comment(
|
|||
.await
|
||||
}
|
||||
|
||||
async fn undo_like_or_dislike_post(
|
||||
actor: &Url,
|
||||
async fn undo_vote_post(
|
||||
actor: Person,
|
||||
post: &Post,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor = get_or_fetch_and_upsert_person(actor, context, request_counter).await?;
|
||||
|
||||
let post_id = post.id;
|
||||
let person_id = actor.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
use crate::activities::{
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::{dislike::DislikePostOrComment, receive_undo_like_or_dislike},
|
||||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UndoDislikePostOrComment {
|
||||
to: PublicUrl,
|
||||
object: DislikePostOrComment,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: UndoType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UndoDislikePostOrComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_urls_match(&self.common.actor, &self.object.common().actor)?;
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
receive_undo_like_or_dislike(
|
||||
&self.common.actor,
|
||||
&self.object.object,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
use crate::activities::{
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::{like::LikePostOrComment, receive_undo_like_or_dislike},
|
||||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UndoLikePostOrComment {
|
||||
to: PublicUrl,
|
||||
object: LikePostOrComment,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: UndoType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UndoLikePostOrComment {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
|
||||
verify_urls_match(&self.common.actor, &self.object.common().actor)?;
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
receive_undo_like_or_dislike(
|
||||
&self.common.actor,
|
||||
&self.object.object,
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
122
crates/apub/src/activities/voting/undo_vote.rs
Normal file
122
crates/apub/src/activities/voting/undo_vote.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::announce::AnnouncableActivities,
|
||||
generate_activity_id,
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::{
|
||||
undo_vote_comment,
|
||||
undo_vote_post,
|
||||
vote::{Vote, VoteType},
|
||||
},
|
||||
},
|
||||
activity_queue::send_to_community_new,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::{
|
||||
objects::get_or_fetch_and_insert_post_or_comment,
|
||||
person::get_or_fetch_and_upsert_person,
|
||||
},
|
||||
ActorType,
|
||||
PostOrComment,
|
||||
};
|
||||
use activitystreams::activity::kind::UndoType;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::{
|
||||
source::{community::Community, person::Person},
|
||||
CommunityId,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UndoVote {
|
||||
to: PublicUrl,
|
||||
object: Vote,
|
||||
cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
kind: UndoType,
|
||||
#[serde(flatten)]
|
||||
common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl UndoVote {
|
||||
pub async fn send(
|
||||
object: &PostOrComment,
|
||||
actor: &Person,
|
||||
community_id: CommunityId,
|
||||
kind: VoteType,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
let id = generate_activity_id(UndoType::Undo)?;
|
||||
|
||||
let undo_vote = UndoVote {
|
||||
to: PublicUrl::Public,
|
||||
object: Vote {
|
||||
to: PublicUrl::Public,
|
||||
object: object.ap_id(),
|
||||
cc: [community.actor_id()],
|
||||
kind: kind.clone(),
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: generate_activity_id(kind)?,
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
},
|
||||
cc: [community.actor_id()],
|
||||
kind: UndoType::Undo,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: id.clone(),
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
let activity = AnnouncableActivities::UndoVote(undo_vote);
|
||||
send_to_community_new(activity, &id, actor, &community, vec![], context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for UndoVote {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
verify_urls_match(&self.common.actor, &self.object.common().actor)?;
|
||||
self.object.verify(context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor =
|
||||
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
||||
let object =
|
||||
get_or_fetch_and_insert_post_or_comment(&self.object.object, context, request_counter)
|
||||
.await?;
|
||||
match object {
|
||||
PostOrComment::Post(p) => undo_vote_post(actor, p.deref(), context).await,
|
||||
PostOrComment::Comment(c) => undo_vote_comment(actor, c.deref(), context).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
133
crates/apub/src/activities/voting/vote.rs
Normal file
133
crates/apub/src/activities/voting/vote.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use crate::{
|
||||
activities::{
|
||||
community::announce::AnnouncableActivities,
|
||||
generate_activity_id,
|
||||
verify_activity,
|
||||
verify_person_in_community,
|
||||
voting::{vote_comment, vote_post},
|
||||
},
|
||||
activity_queue::send_to_community_new,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::{
|
||||
objects::get_or_fetch_and_insert_post_or_comment,
|
||||
person::get_or_fetch_and_upsert_person,
|
||||
},
|
||||
ActorType,
|
||||
PostOrComment,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_db_queries::Crud;
|
||||
use lemmy_db_schema::{
|
||||
source::{community::Community, person::Person},
|
||||
CommunityId,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, ops::Deref};
|
||||
use strum_macros::ToString;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, ToString, Deserialize, Serialize)]
|
||||
pub enum VoteType {
|
||||
Like,
|
||||
Dislike,
|
||||
}
|
||||
|
||||
impl TryFrom<i16> for VoteType {
|
||||
type Error = LemmyError;
|
||||
|
||||
fn try_from(value: i16) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
1 => Ok(VoteType::Like),
|
||||
-1 => Ok(VoteType::Dislike),
|
||||
_ => Err(anyhow!("invalid vote value").into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VoteType> for i16 {
|
||||
fn from(value: &VoteType) -> i16 {
|
||||
match value {
|
||||
VoteType::Like => 1,
|
||||
VoteType::Dislike => -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Vote {
|
||||
pub(in crate::activities::voting) to: PublicUrl,
|
||||
pub(in crate::activities::voting) object: Url,
|
||||
pub(in crate::activities::voting) cc: [Url; 1],
|
||||
#[serde(rename = "type")]
|
||||
pub(in crate::activities::voting) kind: VoteType,
|
||||
#[serde(flatten)]
|
||||
pub(in crate::activities::voting) common: ActivityCommonFields,
|
||||
}
|
||||
|
||||
impl Vote {
|
||||
pub async fn send(
|
||||
object: &PostOrComment,
|
||||
actor: &Person,
|
||||
community_id: CommunityId,
|
||||
kind: VoteType,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
let id = generate_activity_id(kind.clone())?;
|
||||
|
||||
let vote = Vote {
|
||||
to: PublicUrl::Public,
|
||||
object: object.ap_id(),
|
||||
cc: [community.actor_id()],
|
||||
kind,
|
||||
common: ActivityCommonFields {
|
||||
context: lemmy_context(),
|
||||
id: id.clone(),
|
||||
actor: actor.actor_id(),
|
||||
unparsed: Default::default(),
|
||||
},
|
||||
};
|
||||
let activity = AnnouncableActivities::Vote(vote);
|
||||
send_to_community_new(activity, &id, actor, &community, vec![], context).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ActivityHandler for Vote {
|
||||
async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_activity(self.common())?;
|
||||
verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let actor =
|
||||
get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?;
|
||||
let object =
|
||||
get_or_fetch_and_insert_post_or_comment(&self.object, context, request_counter).await?;
|
||||
match object {
|
||||
PostOrComment::Post(p) => vote_post(&self.kind, actor, p.deref(), context).await,
|
||||
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, c.deref(), context).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn common(&self) -> &ActivityCommonFields {
|
||||
&self.common
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{
|
||||
activities::community::announce::{AnnouncableActivities, AnnounceActivity},
|
||||
check_is_apub_id_valid,
|
||||
extensions::signatures::sign_and_send,
|
||||
insert_activity,
|
||||
|
@ -24,7 +25,7 @@ use itertools::Itertools;
|
|||
use lemmy_db_schema::source::{community::Community, person::Person};
|
||||
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::{debug, warn};
|
||||
use log::{debug, info, warn};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
|
||||
|
@ -137,37 +138,77 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends notification to any persons mentioned in a comment
|
||||
///
|
||||
/// * `creator` person who created the comment
|
||||
/// * `mentions` list of inboxes of persons which are mentioned in the comment
|
||||
/// * `activity` either a `Create/Note` or `Update/Note`
|
||||
pub(crate) async fn send_comment_mentions<T, Kind>(
|
||||
creator: &Person,
|
||||
mentions: Vec<Url>,
|
||||
activity: T,
|
||||
pub(crate) async fn send_to_community_new(
|
||||
activity: AnnouncableActivities,
|
||||
activity_id: &Url,
|
||||
actor: &dyn ActorType,
|
||||
community: &Community,
|
||||
additional_inboxes: Vec<Url>,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
// if this is a local community, we need to do an announce from the community instead
|
||||
if community.local {
|
||||
insert_activity(activity_id, activity.clone(), true, false, context.pool()).await?;
|
||||
AnnounceActivity::send(activity, community, additional_inboxes, context).await?;
|
||||
} else {
|
||||
let mut inboxes = additional_inboxes;
|
||||
inboxes.push(community.get_shared_inbox_or_inbox_url());
|
||||
send_activity_new(context, &activity, activity_id, actor, inboxes, false).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn send_activity_new<T>(
|
||||
context: &LemmyContext,
|
||||
activity: &T,
|
||||
activity_id: &Url,
|
||||
actor: &dyn ActorType,
|
||||
inboxes: Vec<Url>,
|
||||
sensitive: bool,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
T: AsObject<Kind> + Extends<Kind> + Debug + BaseExt<Kind>,
|
||||
Kind: Serialize,
|
||||
<T as Extends<Kind>>::Error: From<serde_json::Error> + Send + Sync + 'static,
|
||||
T: Serialize,
|
||||
{
|
||||
debug!(
|
||||
"Sending mentions activity {:?} to {:?}",
|
||||
&activity.id_unchecked(),
|
||||
&mentions
|
||||
);
|
||||
let mentions = mentions
|
||||
if !Settings::get().federation.enabled || inboxes.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Sending activity {}", activity_id.to_string());
|
||||
|
||||
// Don't send anything to ourselves
|
||||
// TODO: this should be a debug assert
|
||||
let hostname = Settings::get().get_hostname_without_port()?;
|
||||
let inboxes: Vec<&Url> = inboxes
|
||||
.iter()
|
||||
.filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
|
||||
.map(|i| i.to_owned())
|
||||
.filter(|i| i.domain().expect("valid inbox url") != hostname)
|
||||
.collect();
|
||||
send_activity_internal(
|
||||
context, activity, creator, mentions, false, // Don't create a new DB row
|
||||
false,
|
||||
|
||||
let serialised_activity = serde_json::to_string(&activity)?;
|
||||
|
||||
insert_activity(
|
||||
activity_id,
|
||||
serialised_activity.clone(),
|
||||
true,
|
||||
sensitive,
|
||||
context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for i in inboxes {
|
||||
let message = SendActivityTask {
|
||||
activity: serialised_activity.to_owned(),
|
||||
inbox: i.to_owned(),
|
||||
actor_id: actor.actor_id(),
|
||||
private_key: actor.private_key().context(location_info!())?,
|
||||
};
|
||||
if env::var("LEMMY_TEST_SEND_SYNC").is_ok() {
|
||||
do_send(message, &Client::default()).await?;
|
||||
} else {
|
||||
context.activity_queue.queue::<SendActivityTask>(message)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use activitystreams::{base::AnyBase, context};
|
||||
use lemmy_utils::LemmyError;
|
||||
use activitystreams::{base::AnyBase, context, primitives::OneOrMany};
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
pub fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
|
||||
pub fn lemmy_context() -> OneOrMany<AnyBase> {
|
||||
let context_ext = AnyBase::from_arbitrary_json(json!(
|
||||
{
|
||||
"sc": "http://schema.org#",
|
||||
|
@ -19,10 +18,11 @@ pub fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
|
|||
"type": "sc:Text",
|
||||
"id": "as:alsoKnownAs"
|
||||
},
|
||||
}))?;
|
||||
Ok(vec![
|
||||
}))
|
||||
.expect("parse context");
|
||||
OneOrMany::from(vec![
|
||||
AnyBase::from(context()),
|
||||
context_ext,
|
||||
AnyBase::from(Url::parse("https://w3id.org/security/v1")?),
|
||||
AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")),
|
||||
])
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod context;
|
||||
pub(crate) mod group_extension;
|
||||
pub(crate) mod page_extension;
|
||||
pub(crate) mod person_extension;
|
||||
pub mod signatures;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use activitystreams::unparsed::UnparsedMutExt;
|
||||
use activitystreams_ext::UnparsedExtension;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Activitystreams extension to allow (de)serializing additional Post fields
|
||||
/// `comemnts_enabled` (called 'locked' in Lemmy),
|
||||
/// `sensitive` (called 'nsfw') and `stickied`.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PageExtension {
|
||||
pub comments_enabled: Option<bool>,
|
||||
pub sensitive: Option<bool>,
|
||||
pub stickied: Option<bool>,
|
||||
}
|
||||
|
||||
impl<U> UnparsedExtension<U> for PageExtension
|
||||
where
|
||||
U: UnparsedMutExt,
|
||||
{
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
|
||||
Ok(PageExtension {
|
||||
comments_enabled: unparsed_mut.remove("commentsEnabled")?,
|
||||
sensitive: unparsed_mut.remove("sensitive")?,
|
||||
stickied: unparsed_mut.remove("stickied")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
|
||||
unparsed_mut.insert("commentsEnabled", self.comments_enabled)?;
|
||||
unparsed_mut.insert("sensitive", self.sensitive)?;
|
||||
unparsed_mut.insert("stickied", self.stickied)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
use crate::{
|
||||
fetcher::fetch::fetch_remote_object,
|
||||
objects::FromApub,
|
||||
NoteExt,
|
||||
PageExt,
|
||||
objects::{comment::Note, post::Page, FromApub},
|
||||
PostOrComment,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
|
@ -35,7 +33,7 @@ pub async fn get_or_fetch_and_insert_post(
|
|||
Err(NotFound {}) => {
|
||||
debug!("Fetching and creating remote post: {}", post_ap_id);
|
||||
let page =
|
||||
fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?;
|
||||
fetch_remote_object::<Page>(context.client(), post_ap_id, recursion_counter).await?;
|
||||
let post = Post::from_apub(
|
||||
&page,
|
||||
context,
|
||||
|
@ -74,7 +72,7 @@ pub async fn get_or_fetch_and_insert_comment(
|
|||
comment_ap_id
|
||||
);
|
||||
let comment =
|
||||
fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?;
|
||||
fetch_remote_object::<Note>(context.client(), comment_ap_id, recursion_counter).await?;
|
||||
let comment = Comment::from_apub(
|
||||
&comment,
|
||||
context,
|
||||
|
|
|
@ -6,11 +6,9 @@ use crate::{
|
|||
is_deleted,
|
||||
},
|
||||
find_object_by_id,
|
||||
objects::FromApub,
|
||||
objects::{comment::Note, post::Page, FromApub},
|
||||
GroupExt,
|
||||
NoteExt,
|
||||
Object,
|
||||
PageExt,
|
||||
PersonExt,
|
||||
};
|
||||
use activitystreams::base::BaseExt;
|
||||
|
@ -46,8 +44,8 @@ use url::Url;
|
|||
enum SearchAcceptedObjects {
|
||||
Person(Box<PersonExt>),
|
||||
Group(Box<GroupExt>),
|
||||
Page(Box<PageExt>),
|
||||
Comment(Box<NoteExt>),
|
||||
Page(Box<Page>),
|
||||
Comment(Box<Note>),
|
||||
}
|
||||
|
||||
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
|
||||
|
|
|
@ -81,7 +81,7 @@ pub(crate) async fn get_apub_community_followers(
|
|||
|
||||
let mut collection = UnorderedCollection::new();
|
||||
collection
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(community.followers_url.into())
|
||||
.set_total_items(community_followers.len() as u64);
|
||||
Ok(create_apub_response(&collection))
|
||||
|
@ -112,7 +112,7 @@ pub(crate) async fn get_apub_community_outbox(
|
|||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_many_items(activities)
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(community.get_outbox_url()?)
|
||||
.set_total_items(len as u64);
|
||||
Ok(create_apub_response(&collection))
|
||||
|
@ -130,7 +130,7 @@ pub(crate) async fn get_apub_community_inbox(
|
|||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_id(community.inbox_url.into())
|
||||
.set_many_contexts(lemmy_context()?);
|
||||
.set_many_contexts(lemmy_context());
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
||||
|
@ -155,13 +155,13 @@ pub(crate) async fn get_apub_community_moderators(
|
|||
|
||||
let moderators: Vec<Url> = moderators
|
||||
.into_iter()
|
||||
.map(|m| m.moderator.actor_id.into_inner())
|
||||
.map(|m| m.moderator.actor_id.into())
|
||||
.collect();
|
||||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_id(generate_moderators_url(&community.actor_id)?.into())
|
||||
.set_total_items(moderators.len() as u64)
|
||||
.set_many_items(moderators)
|
||||
.set_many_contexts(lemmy_context()?);
|
||||
.set_many_contexts(lemmy_context());
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::activities::{
|
||||
comment::{create::CreateComment, update::UpdateComment},
|
||||
comment::create_or_update::CreateOrUpdateComment,
|
||||
community::{
|
||||
add_mod::AddMod,
|
||||
announce::AnnounceActivity,
|
||||
|
@ -9,23 +9,17 @@ use crate::activities::{
|
|||
},
|
||||
deletion::{delete::DeletePostCommentOrCommunity, undo_delete::UndoDeletePostCommentOrCommunity},
|
||||
following::{accept::AcceptFollowCommunity, follow::FollowCommunity, undo::UndoFollowCommunity},
|
||||
post::{create::CreatePost, update::UpdatePost},
|
||||
post::create_or_update::CreateOrUpdatePost,
|
||||
private_message::{
|
||||
create::CreatePrivateMessage,
|
||||
create_or_update::CreateOrUpdatePrivateMessage,
|
||||
delete::DeletePrivateMessage,
|
||||
undo_delete::UndoDeletePrivateMessage,
|
||||
update::UpdatePrivateMessage,
|
||||
},
|
||||
removal::{
|
||||
remove::RemovePostCommentCommunityOrMod,
|
||||
undo_remove::UndoRemovePostCommentOrCommunity,
|
||||
},
|
||||
voting::{
|
||||
dislike::DislikePostOrComment,
|
||||
like::LikePostOrComment,
|
||||
undo_dislike::UndoDislikePostOrComment,
|
||||
undo_like::UndoLikePostOrComment,
|
||||
},
|
||||
voting::{undo_vote::UndoVote, vote::Vote},
|
||||
};
|
||||
use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler};
|
||||
use lemmy_utils::LemmyError;
|
||||
|
@ -36,8 +30,7 @@ use serde::{Deserialize, Serialize};
|
|||
#[serde(untagged)]
|
||||
pub enum PersonInboxActivities {
|
||||
AcceptFollowCommunity(AcceptFollowCommunity),
|
||||
CreatePrivateMessage(CreatePrivateMessage),
|
||||
UpdatePrivateMessage(UpdatePrivateMessage),
|
||||
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
|
||||
DeletePrivateMessage(DeletePrivateMessage),
|
||||
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
|
||||
AnnounceActivity(Box<AnnounceActivity>),
|
||||
|
@ -48,14 +41,10 @@ pub enum PersonInboxActivities {
|
|||
pub enum GroupInboxActivities {
|
||||
FollowCommunity(FollowCommunity),
|
||||
UndoFollowCommunity(UndoFollowCommunity),
|
||||
CreateComment(CreateComment),
|
||||
UpdateComment(UpdateComment),
|
||||
CreatePost(CreatePost),
|
||||
UpdatePost(UpdatePost),
|
||||
LikePostOrComment(LikePostOrComment),
|
||||
DislikePostOrComment(DislikePostOrComment),
|
||||
UndoLikePostOrComment(UndoLikePostOrComment),
|
||||
UndoDislikePostOrComment(UndoDislikePostOrComment),
|
||||
CreateOrUpdateComment(CreateOrUpdateComment),
|
||||
CreateOrUpdatePost(Box<CreateOrUpdatePost>),
|
||||
Vote(Vote),
|
||||
UndoVote(UndoVote),
|
||||
DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
|
||||
UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
|
||||
RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
|
||||
|
@ -72,14 +61,10 @@ pub enum SharedInboxActivities {
|
|||
// received by group
|
||||
FollowCommunity(FollowCommunity),
|
||||
UndoFollowCommunity(UndoFollowCommunity),
|
||||
CreateComment(CreateComment),
|
||||
UpdateComment(UpdateComment),
|
||||
CreatePost(CreatePost),
|
||||
UpdatePost(UpdatePost),
|
||||
LikePostOrComment(LikePostOrComment),
|
||||
DislikePostOrComment(DislikePostOrComment),
|
||||
UndoDislikePostOrComment(UndoDislikePostOrComment),
|
||||
UndoLikePostOrComment(UndoLikePostOrComment),
|
||||
CreateOrUpdateComment(CreateOrUpdateComment),
|
||||
CreateOrUpdatePost(Box<CreateOrUpdatePost>),
|
||||
Vote(Vote),
|
||||
UndoVote(UndoVote),
|
||||
DeletePostCommentOrCommunity(DeletePostCommentOrCommunity),
|
||||
UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity),
|
||||
RemovePostCommentOrCommunity(RemovePostCommentCommunityOrMod),
|
||||
|
@ -92,8 +77,7 @@ pub enum SharedInboxActivities {
|
|||
AcceptFollowCommunity(AcceptFollowCommunity),
|
||||
// Note, pm activities need to be at the end, otherwise comments will end up here. We can probably
|
||||
// avoid this problem by replacing createpm.object with our own struct, instead of NoteExt.
|
||||
CreatePrivateMessage(CreatePrivateMessage),
|
||||
UpdatePrivateMessage(UpdatePrivateMessage),
|
||||
CreateOrUpdatePrivateMessage(CreateOrUpdatePrivateMessage),
|
||||
DeletePrivateMessage(DeletePrivateMessage),
|
||||
UndoDeletePrivateMessage(UndoDeletePrivateMessage),
|
||||
AnnounceActivity(Box<AnnounceActivity>),
|
||||
|
|
|
@ -71,7 +71,7 @@ pub(crate) async fn get_apub_person_outbox(
|
|||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_many_items(Vec::<Url>::new())
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(person.get_outbox_url()?)
|
||||
.set_total_items(0_u64);
|
||||
Ok(create_apub_response(&collection))
|
||||
|
@ -89,6 +89,6 @@ pub(crate) async fn get_apub_person_inbox(
|
|||
let mut collection = OrderedCollection::new();
|
||||
collection
|
||||
.set_id(person.inbox_url.into())
|
||||
.set_many_contexts(lemmy_context()?);
|
||||
.set_many_contexts(lemmy_context());
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ pub mod objects;
|
|||
use crate::{
|
||||
extensions::{
|
||||
group_extension::GroupExtension,
|
||||
page_extension::PageExtension,
|
||||
person_extension::PersonExtension,
|
||||
signatures::{PublicKey, PublicKeyExtension},
|
||||
},
|
||||
|
@ -21,9 +20,9 @@ use activitystreams::{
|
|||
activity::Follow,
|
||||
actor,
|
||||
base::AnyBase,
|
||||
object::{ApObject, AsObject, Note, ObjectExt, Page},
|
||||
object::{ApObject, AsObject, ObjectExt},
|
||||
};
|
||||
use activitystreams_ext::{Ext1, Ext2};
|
||||
use activitystreams_ext::Ext2;
|
||||
use anyhow::{anyhow, Context};
|
||||
use diesel::NotFound;
|
||||
use lemmy_api_common::blocking;
|
||||
|
@ -54,9 +53,6 @@ pub type GroupExt =
|
|||
type PersonExt =
|
||||
Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
|
||||
pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
|
||||
/// Activitystreams type for post
|
||||
pub type PageExt = Ext1<ApObject<Page>, PageExtension>;
|
||||
pub type NoteExt = ApObject<Note>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
|
||||
pub enum UserTypes {
|
||||
|
@ -133,10 +129,6 @@ pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Resu
|
|||
/// and actors in Lemmy.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ApubObjectType {
|
||||
async fn send_create(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_update(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_delete(&self, creator: &DbPerson, context: &LemmyContext)
|
||||
-> Result<(), LemmyError>;
|
||||
async fn send_undo_delete(
|
||||
|
@ -152,21 +144,6 @@ pub trait ApubObjectType {
|
|||
) -> Result<(), LemmyError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait ApubLikeableType {
|
||||
async fn send_like(&self, creator: &DbPerson, context: &LemmyContext) -> Result<(), LemmyError>;
|
||||
async fn send_dislike(
|
||||
&self,
|
||||
creator: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
async fn send_undo_like(
|
||||
&self,
|
||||
creator: &DbPerson,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError>;
|
||||
}
|
||||
|
||||
/// Common methods provided by ActivityPub actors (community and person). Not all methods are
|
||||
/// implemented by all actors.
|
||||
pub trait ActorType {
|
||||
|
@ -314,7 +291,7 @@ pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
|||
}
|
||||
|
||||
pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, LemmyError> {
|
||||
let actor_id = actor_id.clone().into_inner();
|
||||
let actor_id: Url = actor_id.clone().into();
|
||||
let url = format!(
|
||||
"{}://{}{}/inbox",
|
||||
&actor_id.scheme(),
|
||||
|
@ -377,6 +354,16 @@ pub enum PostOrComment {
|
|||
Post(Box<Post>),
|
||||
}
|
||||
|
||||
impl PostOrComment {
|
||||
pub(crate) fn ap_id(&self) -> Url {
|
||||
match self {
|
||||
PostOrComment::Post(p) => p.ap_id.clone(),
|
||||
PostOrComment::Comment(c) => c.ap_id.clone(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to find a post or comment in the local database, without any network requests.
|
||||
/// This is used to handle deletions and removals, because in case we dont have the object, we can
|
||||
/// simply ignore the activity.
|
||||
|
|
|
@ -1,29 +1,24 @@
|
|||
use crate::{
|
||||
activities::verify_person_in_community,
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
|
||||
get_community_from_to_or_cc,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
check_object_for_community_or_site_ban,
|
||||
create_tombstone,
|
||||
get_object_from_apub,
|
||||
get_or_fetch_and_upsert_person,
|
||||
get_source_markdown_value,
|
||||
set_content_and_source,
|
||||
FromApub,
|
||||
FromApubToForm,
|
||||
ToApub,
|
||||
},
|
||||
NoteExt,
|
||||
objects::{create_tombstone, get_or_fetch_and_upsert_person, FromApub, Source, ToApub},
|
||||
ActorType,
|
||||
};
|
||||
use activitystreams::{
|
||||
object::{kind::NoteType, ApObject, Note, Tombstone},
|
||||
prelude::*,
|
||||
public,
|
||||
base::AnyBase,
|
||||
object::{kind::NoteType, Tombstone},
|
||||
primitives::OneOrMany,
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_apub_lib::{
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown, PublicUrl},
|
||||
verify_domains_match,
|
||||
};
|
||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentForm},
|
||||
|
@ -39,24 +34,103 @@ use lemmy_utils::{
|
|||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Note {
|
||||
#[serde(rename = "@context")]
|
||||
context: OneOrMany<AnyBase>,
|
||||
r#type: NoteType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: Url,
|
||||
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
|
||||
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
|
||||
/// the post in [`in_reply_to`]).
|
||||
to: PublicUrl,
|
||||
content: String,
|
||||
media_type: MediaTypeHtml,
|
||||
source: Source,
|
||||
in_reply_to: Vec<Url>,
|
||||
published: DateTime<FixedOffset>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Note {
|
||||
async fn get_parents(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(Post, Option<CommentId>), LemmyError> {
|
||||
// This post, or the parent comment might not yet exist on this server yet, fetch them.
|
||||
let post_id = self.in_reply_to.get(0).context(location_info!())?;
|
||||
let post = Box::pin(get_or_fetch_and_insert_post(
|
||||
post_id,
|
||||
context,
|
||||
request_counter,
|
||||
))
|
||||
.await?;
|
||||
|
||||
// The 2nd item, if it exists, is the parent comment apub_id
|
||||
// Nested comments will automatically get fetched recursively
|
||||
let parent_id: Option<CommentId> = match self.in_reply_to.get(1) {
|
||||
Some(parent_comment_uri) => {
|
||||
let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
|
||||
parent_comment_uri,
|
||||
context,
|
||||
request_counter,
|
||||
))
|
||||
.await?;
|
||||
|
||||
Some(parent_comment.id)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok((post, parent_id))
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if post.locked {
|
||||
return Err(anyhow!("Post is locked").into());
|
||||
}
|
||||
verify_domains_match(&self.attributed_to, &self.id)?;
|
||||
verify_person_in_community(
|
||||
&self.attributed_to,
|
||||
&community.actor_id(),
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ToApub for Comment {
|
||||
type ApubType = NoteExt;
|
||||
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<NoteExt, LemmyError> {
|
||||
let mut comment = ApObject::new(Note::new());
|
||||
type ApubType = Note;
|
||||
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let post_id = self.post_id;
|
||||
let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
let community_id = post.community_id;
|
||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||
|
||||
// Add a vector containing some important info to the "in_reply_to" field
|
||||
// [post_ap_id, Option(parent_comment_ap_id)]
|
||||
let mut in_reply_to_vec = vec![post.ap_id.into_inner()];
|
||||
|
@ -67,23 +141,25 @@ impl ToApub for Comment {
|
|||
in_reply_to_vec.push(parent_comment.ap_id.into_inner());
|
||||
}
|
||||
|
||||
comment
|
||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published))
|
||||
// NOTE: included community id for compatibility with lemmy v0.9.9
|
||||
.set_many_tos(vec![community.actor_id.into_inner(), public()])
|
||||
.set_many_in_reply_tos(in_reply_to_vec)
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
let note = Note {
|
||||
context: lemmy_context(),
|
||||
r#type: NoteType::Note,
|
||||
id: self.ap_id.to_owned().into_inner(),
|
||||
attributed_to: creator.actor_id.into_inner(),
|
||||
to: PublicUrl::Public,
|
||||
content: self.content.clone(),
|
||||
media_type: MediaTypeHtml::Html,
|
||||
source: Source {
|
||||
content: self.content.clone(),
|
||||
media_type: MediaTypeMarkdown::Markdown,
|
||||
},
|
||||
in_reply_to: in_reply_to_vec,
|
||||
published: convert_datetime(self.published),
|
||||
updated: self.updated.map(convert_datetime),
|
||||
unparsed: Default::default(),
|
||||
};
|
||||
|
||||
set_content_and_source(&mut comment, &self.content)?;
|
||||
|
||||
if let Some(u) = self.updated {
|
||||
comment.set_updated(convert_datetime(u));
|
||||
}
|
||||
|
||||
Ok(comment)
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
|
@ -98,108 +174,38 @@ impl ToApub for Comment {
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApub for Comment {
|
||||
type ApubType = NoteExt;
|
||||
type ApubType = Note;
|
||||
|
||||
/// Converts a `Note` to `Comment`.
|
||||
///
|
||||
/// If the parent community, post and comment(s) are not known locally, these are also fetched.
|
||||
async fn from_apub(
|
||||
note: &NoteExt,
|
||||
note: &Note,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
mod_action_allowed: bool,
|
||||
) -> Result<Comment, LemmyError> {
|
||||
let comment: Comment = get_object_from_apub(
|
||||
note,
|
||||
context,
|
||||
expected_domain,
|
||||
request_counter,
|
||||
mod_action_allowed,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let post_id = comment.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
check_object_for_community_or_site_ban(note, post.community_id, context, request_counter)
|
||||
.await?;
|
||||
Ok(comment)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApubToForm<NoteExt> for CommentForm {
|
||||
async fn from_apub(
|
||||
note: &NoteExt,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
_expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
_mod_action_allowed: bool,
|
||||
) -> Result<CommentForm, LemmyError> {
|
||||
let community = get_community_from_to_or_cc(note, context, request_counter).await?;
|
||||
let ap_id = Some(check_object_domain(note, expected_domain, community.local)?);
|
||||
let creator_actor_id = ¬e
|
||||
.attributed_to()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
) -> Result<Comment, LemmyError> {
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
|
||||
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
||||
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
|
||||
|
||||
let mut in_reply_tos = note
|
||||
.in_reply_to()
|
||||
.as_ref()
|
||||
.context(location_info!())?
|
||||
.as_many()
|
||||
.context(location_info!())?
|
||||
.iter()
|
||||
.map(|i| i.as_xsd_any_uri().context(""));
|
||||
let post_ap_id = in_reply_tos.next().context(location_info!())??;
|
||||
let content = ¬e.source.content;
|
||||
let content_slurs_removed = remove_slurs(content);
|
||||
|
||||
// This post, or the parent comment might not yet exist on this server yet, fetch them.
|
||||
let post = Box::pin(get_or_fetch_and_insert_post(
|
||||
post_ap_id,
|
||||
context,
|
||||
request_counter,
|
||||
))
|
||||
.await?;
|
||||
if post.locked {
|
||||
return Err(anyhow!("Post is locked").into());
|
||||
}
|
||||
|
||||
// The 2nd item, if it exists, is the parent comment apub_id
|
||||
// For deeply nested comments, FromApub automatically gets called recursively
|
||||
let parent_id: Option<CommentId> = match in_reply_tos.next() {
|
||||
Some(parent_comment_uri) => {
|
||||
let parent_comment_ap_id = &parent_comment_uri?;
|
||||
let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
|
||||
parent_comment_ap_id,
|
||||
context,
|
||||
request_counter,
|
||||
))
|
||||
.await?;
|
||||
|
||||
Some(parent_comment.id)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let content = get_source_markdown_value(note)?.context(location_info!())?;
|
||||
let content_slurs_removed = remove_slurs(&content);
|
||||
|
||||
Ok(CommentForm {
|
||||
let form = CommentForm {
|
||||
creator_id: creator.id,
|
||||
post_id: post.id,
|
||||
parent_id,
|
||||
parent_id: parent_comment_id,
|
||||
content: content_slurs_removed,
|
||||
removed: None,
|
||||
read: None,
|
||||
published: note.published().map(|u| u.to_owned().naive_local()),
|
||||
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
||||
published: Some(note.published.naive_local()),
|
||||
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
||||
deleted: None,
|
||||
ap_id,
|
||||
ap_id: Some(note.id.clone().into()),
|
||||
local: Some(false),
|
||||
})
|
||||
};
|
||||
Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ impl ToApub for Community {
|
|||
|
||||
let mut group = ApObject::new(Group::new());
|
||||
group
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(self.actor_id.to_owned().into())
|
||||
.set_name(self.title.to_owned())
|
||||
.set_published(convert_datetime(self.published))
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use crate::{
|
||||
check_community_or_site_ban,
|
||||
check_is_apub_id_valid,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
};
|
||||
use crate::{check_is_apub_id_valid, fetcher::person::get_or_fetch_and_upsert_person};
|
||||
use activitystreams::{
|
||||
base::{AsBase, BaseExt, ExtendsExt},
|
||||
markers::Base,
|
||||
|
@ -12,8 +8,9 @@ use activitystreams::{
|
|||
use anyhow::{anyhow, Context};
|
||||
use chrono::NaiveDateTime;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||
use lemmy_db_schema::{CommunityId, DbUrl};
|
||||
use lemmy_db_schema::DbUrl;
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
settings::structs::Settings,
|
||||
|
@ -70,6 +67,13 @@ pub trait FromApubToForm<ApubType> {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
content: String,
|
||||
media_type: MediaTypeMarkdown,
|
||||
}
|
||||
|
||||
/// Updated is actually the deletion time
|
||||
fn create_tombstone<T>(
|
||||
deleted: bool,
|
||||
|
@ -211,21 +215,3 @@ where
|
|||
Ok(to)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
|
||||
object: &T,
|
||||
community_id: CommunityId,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
T: ObjectExt<Kind>,
|
||||
{
|
||||
let person_id = object
|
||||
.attributed_to()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
|
||||
check_community_or_site_ban(&person, community_id, context.pool()).await
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ impl ToApub for DbPerson {
|
|||
let mut person = ApObject::new(actor);
|
||||
|
||||
person
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_many_contexts(lemmy_context())
|
||||
.set_id(self.actor_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published));
|
||||
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
extensions::{context::lemmy_context, page_extension::PageExtension},
|
||||
activities::{extract_community, verify_person_in_community},
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
get_community_from_to_or_cc,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
check_object_for_community_or_site_ban,
|
||||
create_tombstone,
|
||||
get_object_from_apub,
|
||||
get_source_markdown_value,
|
||||
set_content_and_source,
|
||||
FromApub,
|
||||
FromApubToForm,
|
||||
ToApub,
|
||||
},
|
||||
PageExt,
|
||||
objects::{create_tombstone, FromApub, Source, ToApub},
|
||||
ActorType,
|
||||
};
|
||||
use activitystreams::{
|
||||
object::{kind::PageType, ApObject, Image, Page, Tombstone},
|
||||
prelude::*,
|
||||
base::AnyBase,
|
||||
object::{
|
||||
kind::{ImageType, PageType},
|
||||
Tombstone,
|
||||
},
|
||||
primitives::OneOrMany,
|
||||
public,
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use activitystreams_ext::Ext1;
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_apub_lib::{
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||
verify_domains_match,
|
||||
};
|
||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||
use lemmy_db_schema::{
|
||||
self,
|
||||
source::{
|
||||
|
@ -34,66 +31,125 @@ use lemmy_db_schema::{
|
|||
},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
location_info,
|
||||
request::fetch_iframely_and_pictrs_data,
|
||||
utils::{check_slurs, convert_datetime, remove_slurs},
|
||||
utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Page {
|
||||
#[serde(rename = "@context")]
|
||||
context: OneOrMany<AnyBase>,
|
||||
r#type: PageType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: Url,
|
||||
to: [Url; 2],
|
||||
name: String,
|
||||
content: Option<String>,
|
||||
media_type: MediaTypeHtml,
|
||||
source: Option<Source>,
|
||||
url: Option<Url>,
|
||||
image: Option<ImageObject>,
|
||||
pub(crate) comments_enabled: Option<bool>,
|
||||
sensitive: Option<bool>,
|
||||
pub(crate) stickied: Option<bool>,
|
||||
published: DateTime<FixedOffset>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImageObject {
|
||||
content: ImageType,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
||||
/// the current value, it is a mod action and needs to be verified as such.
|
||||
///
|
||||
/// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]].
|
||||
pub(crate) async fn is_mod_action(&self, pool: &DbPool) -> Result<bool, LemmyError> {
|
||||
let post_id = self.id.clone();
|
||||
let old_post = blocking(pool, move |conn| {
|
||||
Post::read_from_apub_id(conn, &post_id.into())
|
||||
})
|
||||
.await?;
|
||||
|
||||
let is_mod_action = if let Ok(old_post) = old_post {
|
||||
self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(is_mod_action)
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = extract_community(&self.to, context, request_counter).await?;
|
||||
|
||||
check_slurs(&self.name)?;
|
||||
verify_domains_match(&self.attributed_to, &self.id)?;
|
||||
verify_person_in_community(
|
||||
&self.attributed_to,
|
||||
&community.actor_id(),
|
||||
context,
|
||||
request_counter,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ToApub for Post {
|
||||
type ApubType = PageExt;
|
||||
type ApubType = Page;
|
||||
|
||||
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
|
||||
let mut page = ApObject::new(Page::new());
|
||||
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<Page, LemmyError> {
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let community_id = self.community_id;
|
||||
let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
|
||||
|
||||
page
|
||||
// Not needed when the Post is embedded in a collection (like for community outbox)
|
||||
// TODO: need to set proper context defining sensitive/commentsEnabled fields
|
||||
// https://git.asonix.dog/Aardwolf/activitystreams/issues/5
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_name(self.name.to_owned())
|
||||
// `summary` field for compatibility with lemmy v0.9.9 and older,
|
||||
// TODO: remove this after some time
|
||||
.set_summary(self.name.to_owned())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_many_tos(vec![community.actor_id.into_inner(), public()])
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
let source = self.body.clone().map(|body| Source {
|
||||
content: body,
|
||||
media_type: MediaTypeMarkdown::Markdown,
|
||||
});
|
||||
let image = self.thumbnail_url.clone().map(|thumb| ImageObject {
|
||||
content: ImageType::Image,
|
||||
url: thumb.into(),
|
||||
});
|
||||
|
||||
if let Some(body) = &self.body {
|
||||
set_content_and_source(&mut page, body)?;
|
||||
}
|
||||
|
||||
if let Some(url) = &self.url {
|
||||
page.set_url::<Url>(url.to_owned().into());
|
||||
}
|
||||
|
||||
if let Some(thumbnail_url) = &self.thumbnail_url {
|
||||
let mut image = Image::new();
|
||||
image.set_url::<Url>(thumbnail_url.to_owned().into());
|
||||
page.set_image(image.into_any_base()?);
|
||||
}
|
||||
|
||||
if let Some(u) = self.updated {
|
||||
page.set_updated(convert_datetime(u));
|
||||
}
|
||||
|
||||
let ext = PageExtension {
|
||||
let page = Page {
|
||||
context: lemmy_context(),
|
||||
r#type: PageType::Page,
|
||||
id: self.ap_id.clone().into(),
|
||||
attributed_to: creator.actor_id.into(),
|
||||
to: [community.actor_id.into(), public()],
|
||||
name: self.name.clone(),
|
||||
content: self.body.as_ref().map(|b| markdown_to_html(b)),
|
||||
media_type: MediaTypeHtml::Html,
|
||||
source,
|
||||
url: self.url.clone().map(|u| u.into()),
|
||||
image,
|
||||
comments_enabled: Some(!self.locked),
|
||||
sensitive: Some(self.nsfw),
|
||||
stickied: Some(self.stickied),
|
||||
published: convert_datetime(self.published),
|
||||
updated: self.updated.map(convert_datetime),
|
||||
unparsed: Default::default(),
|
||||
};
|
||||
Ok(Ext1::new(page, ext))
|
||||
Ok(page)
|
||||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
|
@ -108,136 +164,50 @@ impl ToApub for Post {
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApub for Post {
|
||||
type ApubType = PageExt;
|
||||
type ApubType = Page;
|
||||
|
||||
/// Converts a `PageExt` to `PostForm`.
|
||||
///
|
||||
/// If the post's community or creator are not known locally, these are also fetched.
|
||||
async fn from_apub(
|
||||
page: &PageExt,
|
||||
page: &Page,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
_expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
mod_action_allowed: bool,
|
||||
_mod_action_allowed: bool,
|
||||
) -> Result<Post, LemmyError> {
|
||||
let post: Post = get_object_from_apub(
|
||||
page,
|
||||
context,
|
||||
expected_domain,
|
||||
request_counter,
|
||||
mod_action_allowed,
|
||||
)
|
||||
.await?;
|
||||
check_object_for_community_or_site_ban(page, post.community_id, context, request_counter)
|
||||
.await?;
|
||||
Ok(post)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApubToForm<PageExt> for PostForm {
|
||||
async fn from_apub(
|
||||
page: &PageExt,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
mod_action_allowed: bool,
|
||||
) -> Result<PostForm, LemmyError> {
|
||||
let community = get_community_from_to_or_cc(page, context, request_counter).await?;
|
||||
let ap_id = if mod_action_allowed {
|
||||
let id = page.id_unchecked().context(location_info!())?;
|
||||
check_is_apub_id_valid(id, community.local)?;
|
||||
id.to_owned().into()
|
||||
} else {
|
||||
check_object_domain(page, expected_domain, community.local)?
|
||||
};
|
||||
let ext = &page.ext_one;
|
||||
let creator_actor_id = page
|
||||
.inner
|
||||
.attributed_to()
|
||||
.as_ref()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
|
||||
get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?;
|
||||
let community = extract_community(&page.to, context, request_counter).await?;
|
||||
|
||||
let thumbnail_url: Option<Url> = match &page.inner.image() {
|
||||
Some(any_image) => Image::from_any_base(
|
||||
any_image
|
||||
.to_owned()
|
||||
.as_one()
|
||||
.context(location_info!())?
|
||||
.to_owned(),
|
||||
)?
|
||||
.context(location_info!())?
|
||||
.url()
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_any_uri()
|
||||
.map(|url| url.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let url = page
|
||||
.inner
|
||||
.url()
|
||||
.map(|u| u.as_single_xsd_any_uri())
|
||||
.flatten()
|
||||
.map(|u| u.to_owned());
|
||||
|
||||
let (embed_title, embed_description, embed_html, pictrs_thumbnail) = if let Some(url) = &url {
|
||||
let (embed, thumb) = fetch_iframely_and_pictrs_data(context.client(), Some(url)).await?;
|
||||
(embed.title, embed.description, embed.html, thumb)
|
||||
let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
|
||||
let (iframely_response, pictrs_thumbnail) = if let Some(url) = &page.url {
|
||||
fetch_iframely_and_pictrs_data(context.client(), Some(url)).await?
|
||||
} else {
|
||||
(None, None, None, thumbnail_url)
|
||||
(None, thumbnail_url)
|
||||
};
|
||||
let (embed_title, embed_description, embed_html) = iframely_response
|
||||
.map(|u| (u.title, u.description, u.html))
|
||||
.unwrap_or((None, None, None));
|
||||
|
||||
let name = page
|
||||
.inner
|
||||
.name()
|
||||
// The following is for compatibility with lemmy v0.9.9 and older
|
||||
// TODO: remove it after some time (along with the map above)
|
||||
.or_else(|| page.inner.summary())
|
||||
.context(location_info!())?
|
||||
.as_single_xsd_string()
|
||||
.context(location_info!())?
|
||||
.to_string();
|
||||
let body = get_source_markdown_value(page)?;
|
||||
|
||||
// TODO: expected_domain is wrong in this case, because it simply takes the domain of the actor
|
||||
// maybe we need to take id_unchecked() if the activity is from community to user?
|
||||
// why did this work before? -> i dont think it did?
|
||||
// -> try to make expected_domain optional and set it null if it is a mod action
|
||||
|
||||
check_slurs(&name)?;
|
||||
let body_slurs_removed = body.map(|b| remove_slurs(&b));
|
||||
Ok(PostForm {
|
||||
name,
|
||||
url: url.map(|u| u.into()),
|
||||
let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
|
||||
let form = PostForm {
|
||||
name: page.name.clone(),
|
||||
url: page.url.clone().map(|u| u.into()),
|
||||
body: body_slurs_removed,
|
||||
creator_id: creator.id,
|
||||
community_id: community.id,
|
||||
removed: None,
|
||||
locked: ext.comments_enabled.map(|e| !e),
|
||||
published: page
|
||||
.inner
|
||||
.published()
|
||||
.as_ref()
|
||||
.map(|u| u.to_owned().naive_local()),
|
||||
updated: page
|
||||
.inner
|
||||
.updated()
|
||||
.as_ref()
|
||||
.map(|u| u.to_owned().naive_local()),
|
||||
locked: page.comments_enabled.map(|e| !e),
|
||||
published: Some(page.published.naive_local()),
|
||||
updated: page.updated.map(|u| u.naive_local()),
|
||||
deleted: None,
|
||||
nsfw: ext.sensitive,
|
||||
stickied: ext.stickied,
|
||||
nsfw: page.sensitive,
|
||||
stickied: page.stickied,
|
||||
embed_title,
|
||||
embed_description,
|
||||
embed_html,
|
||||
thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
|
||||
ap_id: Some(ap_id),
|
||||
ap_id: Some(page.id.clone().into()),
|
||||
local: Some(false),
|
||||
})
|
||||
};
|
||||
Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +1,93 @@
|
|||
use crate::{
|
||||
extensions::context::lemmy_context,
|
||||
fetcher::person::get_or_fetch_and_upsert_person,
|
||||
objects::{
|
||||
check_object_domain,
|
||||
create_tombstone,
|
||||
get_object_from_apub,
|
||||
get_source_markdown_value,
|
||||
set_content_and_source,
|
||||
FromApub,
|
||||
FromApubToForm,
|
||||
ToApub,
|
||||
},
|
||||
NoteExt,
|
||||
objects::{create_tombstone, FromApub, Source, ToApub},
|
||||
};
|
||||
use activitystreams::{
|
||||
object::{kind::NoteType, ApObject, Note, Tombstone},
|
||||
prelude::*,
|
||||
base::AnyBase,
|
||||
object::{kind::NoteType, Tombstone},
|
||||
primitives::OneOrMany,
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_db_queries::{Crud, DbPool};
|
||||
use lemmy_apub_lib::{
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||
verify_domains_match,
|
||||
};
|
||||
use lemmy_db_queries::{ApubObject, Crud, DbPool};
|
||||
use lemmy_db_schema::source::{
|
||||
person::Person,
|
||||
private_message::{PrivateMessage, PrivateMessageForm},
|
||||
};
|
||||
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
|
||||
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Note {
|
||||
#[serde(rename = "@context")]
|
||||
context: OneOrMany<AnyBase>,
|
||||
r#type: NoteType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: Url,
|
||||
to: Url,
|
||||
content: String,
|
||||
media_type: MediaTypeHtml,
|
||||
source: Source,
|
||||
published: DateTime<FixedOffset>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_domains_match(&self.attributed_to, &self.id)?;
|
||||
let person =
|
||||
get_or_fetch_and_upsert_person(&self.attributed_to, context, request_counter).await?;
|
||||
if person.banned {
|
||||
return Err(anyhow!("Person is banned from site").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ToApub for PrivateMessage {
|
||||
type ApubType = NoteExt;
|
||||
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<NoteExt, LemmyError> {
|
||||
let mut private_message = ApObject::new(Note::new());
|
||||
type ApubType = Note;
|
||||
|
||||
async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
|
||||
let creator_id = self.creator_id;
|
||||
let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
|
||||
|
||||
let recipient_id = self.recipient_id;
|
||||
let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??;
|
||||
|
||||
private_message
|
||||
.set_many_contexts(lemmy_context()?)
|
||||
.set_id(self.ap_id.to_owned().into_inner())
|
||||
.set_published(convert_datetime(self.published))
|
||||
.set_to(recipient.actor_id.into_inner())
|
||||
.set_attributed_to(creator.actor_id.into_inner());
|
||||
|
||||
set_content_and_source(&mut private_message, &self.content)?;
|
||||
|
||||
if let Some(u) = self.updated {
|
||||
private_message.set_updated(convert_datetime(u));
|
||||
}
|
||||
|
||||
Ok(private_message)
|
||||
let note = Note {
|
||||
context: lemmy_context(),
|
||||
r#type: NoteType::Note,
|
||||
id: self.ap_id.clone().into(),
|
||||
attributed_to: creator.actor_id.into_inner(),
|
||||
to: recipient.actor_id.into(),
|
||||
content: self.content.clone(),
|
||||
media_type: MediaTypeHtml::Html,
|
||||
source: Source {
|
||||
content: self.content.clone(),
|
||||
media_type: MediaTypeMarkdown::Markdown,
|
||||
},
|
||||
published: convert_datetime(self.published),
|
||||
updated: self.updated.map(convert_datetime),
|
||||
unparsed: Default::default(),
|
||||
};
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
|
||||
|
@ -69,66 +102,35 @@ impl ToApub for PrivateMessage {
|
|||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApub for PrivateMessage {
|
||||
type ApubType = NoteExt;
|
||||
type ApubType = Note;
|
||||
|
||||
async fn from_apub(
|
||||
note: &NoteExt,
|
||||
note: &Note,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
mod_action_allowed: bool,
|
||||
) -> Result<PrivateMessage, LemmyError> {
|
||||
get_object_from_apub(
|
||||
note,
|
||||
context,
|
||||
expected_domain,
|
||||
request_counter,
|
||||
mod_action_allowed,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FromApubToForm<NoteExt> for PrivateMessageForm {
|
||||
async fn from_apub(
|
||||
note: &NoteExt,
|
||||
context: &LemmyContext,
|
||||
expected_domain: Url,
|
||||
_expected_domain: Url,
|
||||
request_counter: &mut i32,
|
||||
_mod_action_allowed: bool,
|
||||
) -> Result<PrivateMessageForm, LemmyError> {
|
||||
let creator_actor_id = note
|
||||
.attributed_to()
|
||||
.context(location_info!())?
|
||||
.clone()
|
||||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
|
||||
) -> Result<PrivateMessage, LemmyError> {
|
||||
let creator =
|
||||
get_or_fetch_and_upsert_person(&creator_actor_id, context, request_counter).await?;
|
||||
let recipient_actor_id = note
|
||||
.to()
|
||||
.context(location_info!())?
|
||||
.clone()
|
||||
.single_xsd_any_uri()
|
||||
.context(location_info!())?;
|
||||
let recipient =
|
||||
get_or_fetch_and_upsert_person(&recipient_actor_id, context, request_counter).await?;
|
||||
let ap_id = Some(check_object_domain(note, expected_domain, false)?);
|
||||
get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?;
|
||||
let recipient = get_or_fetch_and_upsert_person(¬e.to, context, request_counter).await?;
|
||||
|
||||
let content = get_source_markdown_value(note)?.context(location_info!())?;
|
||||
|
||||
Ok(PrivateMessageForm {
|
||||
let form = PrivateMessageForm {
|
||||
creator_id: creator.id,
|
||||
recipient_id: recipient.id,
|
||||
content,
|
||||
published: note.published().map(|u| u.to_owned().naive_local()),
|
||||
updated: note.updated().map(|u| u.to_owned().naive_local()),
|
||||
content: note.source.content.clone(),
|
||||
published: Some(note.published.naive_local()),
|
||||
updated: note.updated.map(|u| u.to_owned().naive_local()),
|
||||
deleted: None,
|
||||
read: None,
|
||||
ap_id,
|
||||
ap_id: Some(note.id.clone().into()),
|
||||
local: Some(false),
|
||||
};
|
||||
Ok(
|
||||
blocking(context.pool(), move |conn| {
|
||||
PrivateMessage::upsert(conn, &form)
|
||||
})
|
||||
.await??,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ activitystreams-ext = "0.1.0-alpha.2"
|
|||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
async-trait = "0.1.42"
|
||||
url = { version = "2.2.1", features = ["serde"] }
|
||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub mod values;
|
||||
|
||||
use activitystreams::{
|
||||
base::AnyBase,
|
||||
error::DomainError,
|
||||
|
@ -9,18 +11,12 @@ use lemmy_utils::LemmyError;
|
|||
use lemmy_websocket::LemmyContext;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub enum PublicUrl {
|
||||
#[serde(rename = "https://www.w3.org/ns/activitystreams#Public")]
|
||||
Public,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActivityCommonFields {
|
||||
#[serde(rename = "@context")]
|
||||
pub context: OneOrMany<AnyBase>,
|
||||
id: Url,
|
||||
pub id: Url,
|
||||
pub actor: Url,
|
||||
|
||||
// unparsed fields
|
||||
|
|
61
crates/apub_lib/src/values/mod.rs
Normal file
61
crates/apub_lib/src/values/mod.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
//! The enums here serve to limit a json string value to a single, hardcoded value which can be
|
||||
//! verified at compilation time. When using it as the type of a struct field, the struct can only
|
||||
//! be constructed or deserialized if the field has the exact same value.
|
||||
//!
|
||||
//! If we used String as the field type, any value would be accepted, and we would have to check
|
||||
//! manually at runtime that it contains the expected value.
|
||||
//!
|
||||
//! The enums in [`activitystreams::activity::kind`] work in the same way, and can be used to
|
||||
//! distinguish different activity types.
|
||||
//!
|
||||
//! In the example below, `MyObject` can only be constructed or
|
||||
//! deserialized if `media_type` is `text/markdown`, but not if it is `text/html`.
|
||||
//!
|
||||
//! ```
|
||||
//! use lemmy_apub_lib::values::MediaTypeMarkdown;
|
||||
//! use serde_json::from_str;
|
||||
//! use serde::{Deserialize, Serialize};
|
||||
//!
|
||||
//! #[derive(Deserialize, Serialize)]
|
||||
//! struct MyObject {
|
||||
//! content: String,
|
||||
//! media_type: MediaTypeMarkdown,
|
||||
//! }
|
||||
//!
|
||||
//! let markdown_json = r#"{"content": "**test**", "media_type": "text/markdown"}"#;
|
||||
//! let from_markdown = from_str::<MyObject>(markdown_json);
|
||||
//! assert!(from_markdown.is_ok());
|
||||
//!
|
||||
//! let markdown_html = r#"{"content": "<b>test</b>", "media_type": "text/html"}"#;
|
||||
//! let from_html = from_str::<MyObject>(markdown_html);
|
||||
//! assert!(from_html.is_err());
|
||||
//! ```
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The identifier used to address activities to the public.
|
||||
///
|
||||
/// <https://www.w3.org/TR/activitypub/#public-addressing>
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum PublicUrl {
|
||||
#[serde(rename = "https://www.w3.org/ns/activitystreams#Public")]
|
||||
Public,
|
||||
}
|
||||
|
||||
/// Media type for markdown text.
|
||||
///
|
||||
/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum MediaTypeMarkdown {
|
||||
#[serde(rename = "text/markdown")]
|
||||
Markdown,
|
||||
}
|
||||
|
||||
/// Media type for HTML text/
|
||||
///
|
||||
/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum MediaTypeHtml {
|
||||
#[serde(rename = "text/html")]
|
||||
Html,
|
||||
}
|
|
@ -117,6 +117,10 @@ pub trait Reportable<Form> {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait DeleteableOrRemoveable {
|
||||
fn blank_out_deleted_or_removed_info(self) -> Self;
|
||||
}
|
||||
|
||||
pub trait ApubObject<Form> {
|
||||
fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error>
|
||||
where
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ApubObject, Crud, Likeable, Saveable};
|
||||
use crate::{ApubObject, Crud, DeleteableOrRemoveable, Likeable, Saveable};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
|
@ -228,6 +228,13 @@ impl Saveable<CommentSavedForm> for CommentSaved {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeleteableOrRemoveable for Comment {
|
||||
fn blank_out_deleted_or_removed_info(mut self) -> Self {
|
||||
self.content = "".into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, Crud, Likeable, Saveable};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ApubObject, Bannable, Crud, Followable, Joinable};
|
||||
use crate::{ApubObject, Bannable, Crud, DeleteableOrRemoveable, Followable, Joinable};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
|
@ -11,6 +11,7 @@ use lemmy_db_schema::{
|
|||
CommunityModeratorForm,
|
||||
CommunityPersonBan,
|
||||
CommunityPersonBanForm,
|
||||
CommunitySafe,
|
||||
},
|
||||
CommunityId,
|
||||
DbUrl,
|
||||
|
@ -199,6 +200,26 @@ impl Joinable<CommunityModeratorForm> for CommunityModerator {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeleteableOrRemoveable for CommunitySafe {
|
||||
fn blank_out_deleted_or_removed_info(mut self) -> Self {
|
||||
self.title = "".into();
|
||||
self.description = None;
|
||||
self.icon = None;
|
||||
self.banner = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl DeleteableOrRemoveable for Community {
|
||||
fn blank_out_deleted_or_removed_info(mut self) -> Self {
|
||||
self.title = "".into();
|
||||
self.description = None;
|
||||
self.icon = None;
|
||||
self.banner = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CommunityModerator_ {
|
||||
fn delete_for_community(
|
||||
conn: &PgConnection,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ApubObject, Crud, Likeable, Readable, Saveable};
|
||||
use crate::{ApubObject, Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
|
@ -260,6 +260,20 @@ impl Readable<PostReadForm> for PostRead {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeleteableOrRemoveable for Post {
|
||||
fn blank_out_deleted_or_removed_info(mut self) -> Self {
|
||||
self.name = "".into();
|
||||
self.url = None;
|
||||
self.body = None;
|
||||
self.embed_title = None;
|
||||
self.embed_description = None;
|
||||
self.embed_html = None;
|
||||
self.thumbnail_url = None;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, source::post::*};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{ApubObject, Crud};
|
||||
use crate::{ApubObject, Crud, DeleteableOrRemoveable};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{naive_now, source::private_message::*, DbUrl, PersonId, PrivateMessageId};
|
||||
|
||||
|
@ -137,6 +137,13 @@ impl PrivateMessage_ for PrivateMessage {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeleteableOrRemoveable for PrivateMessage {
|
||||
fn blank_out_deleted_or_removed_info(mut self) -> Self {
|
||||
self.content = "".into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{establish_unpooled_connection, source::private_message::PrivateMessage_, Crud};
|
||||
|
|
|
@ -92,6 +92,7 @@ where
|
|||
}
|
||||
|
||||
impl DbUrl {
|
||||
// TODO: remove this method and just use into()
|
||||
pub fn into_inner(self) -> Url {
|
||||
self.0
|
||||
}
|
||||
|
@ -99,7 +100,7 @@ impl DbUrl {
|
|||
|
||||
impl Display for DbUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.to_owned().into_inner().fmt(f)
|
||||
self.to_owned().0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ where
|
|||
response.expect("retry http request")
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct IframelyResponse {
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
|
@ -70,7 +70,7 @@ pub(crate) async fn fetch_iframely(
|
|||
.map_err(|e| RecvError(e.to_string()))?;
|
||||
Ok(res)
|
||||
} else {
|
||||
Ok(IframelyResponse::default())
|
||||
Err(anyhow!("Missing Iframely URL in config.").into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,14 +119,15 @@ pub(crate) async fn fetch_pictrs(
|
|||
pub async fn fetch_iframely_and_pictrs_data(
|
||||
client: &Client,
|
||||
url: Option<&Url>,
|
||||
) -> Result<(IframelyResponse, Option<Url>), LemmyError> {
|
||||
) -> Result<(Option<IframelyResponse>, Option<Url>), LemmyError> {
|
||||
match &url {
|
||||
Some(url) => {
|
||||
// Fetch iframely data
|
||||
let iframely_response = fetch_iframely(client, url).await?;
|
||||
let iframely_res_option = fetch_iframely(client, url).await.ok();
|
||||
|
||||
// Fetch pictrs thumbnail
|
||||
let pictrs_hash = match &iframely_response.thumbnail_url {
|
||||
let pictrs_hash = match &iframely_res_option {
|
||||
Some(iframely_res) => match &iframely_res.thumbnail_url {
|
||||
Some(iframely_thumbnail_url) => fetch_pictrs(client, iframely_thumbnail_url)
|
||||
.await?
|
||||
.map(|r| r.files[0].file.to_owned()),
|
||||
|
@ -134,6 +135,10 @@ pub async fn fetch_iframely_and_pictrs_data(
|
|||
None => fetch_pictrs(client, url)
|
||||
.await?
|
||||
.map(|r| r.files[0].file.to_owned()),
|
||||
},
|
||||
None => fetch_pictrs(client, url)
|
||||
.await?
|
||||
.map(|r| r.files[0].file.to_owned()),
|
||||
};
|
||||
|
||||
// The full urls are necessary for federation
|
||||
|
@ -147,22 +152,10 @@ pub async fn fetch_iframely_and_pictrs_data(
|
|||
.ok()
|
||||
})
|
||||
.flatten();
|
||||
/*
|
||||
let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
|
||||
Some(Url::parse(&format!(
|
||||
"{}/pictrs/image/{}",
|
||||
Settings::get().get_protocol_and_hostname(),
|
||||
pictrs_hash
|
||||
))?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
Ok((iframely_response, pictrs_thumbnail))
|
||||
Ok((iframely_res_option, pictrs_thumbnail))
|
||||
}
|
||||
None => Ok((IframelyResponse::default(), None)),
|
||||
None => Ok((None, None)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ services:
|
|||
restart: always
|
||||
|
||||
lemmy:
|
||||
image: dessalines/lemmy:0.11.2
|
||||
image: dessalines/lemmy:0.11.3
|
||||
ports:
|
||||
- "127.0.0.1:8536:8536"
|
||||
restart: always
|
||||
|
@ -26,7 +26,7 @@ services:
|
|||
- iframely
|
||||
|
||||
lemmy-ui:
|
||||
image: dessalines/lemmy-ui:0.11.2
|
||||
image: dessalines/lemmy-ui:0.11.3
|
||||
ports:
|
||||
- "127.0.0.1:1235:1234"
|
||||
restart: always
|
||||
|
|
28
migrations/2021-08-02-002342_comment_count_fixes/down.sql
Normal file
28
migrations/2021-08-02-002342_comment_count_fixes/down.sql
Normal file
|
@ -0,0 +1,28 @@
|
|||
drop trigger post_aggregates_comment_set_deleted on comment;
|
||||
drop function post_aggregates_comment_deleted;
|
||||
|
||||
create or replace function post_aggregates_comment_count()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
update post_aggregates pa
|
||||
set comments = comments + 1,
|
||||
newest_comment_time = NEW.published
|
||||
where pa.post_id = NEW.post_id;
|
||||
|
||||
-- A 2 day necro-bump limit
|
||||
update post_aggregates pa
|
||||
set newest_comment_time_necro = NEW.published
|
||||
where pa.post_id = NEW.post_id
|
||||
and published > ('now'::timestamp - '2 days'::interval);
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
-- Join to post because that post may not exist anymore
|
||||
update post_aggregates pa
|
||||
set comments = comments - 1
|
||||
from post p
|
||||
where pa.post_id = p.id
|
||||
and pa.post_id = OLD.post_id;
|
||||
END IF;
|
||||
return null;
|
||||
end $$;
|
61
migrations/2021-08-02-002342_comment_count_fixes/up.sql
Normal file
61
migrations/2021-08-02-002342_comment_count_fixes/up.sql
Normal file
|
@ -0,0 +1,61 @@
|
|||
-- Creating a new trigger for when comment.deleted is updated
|
||||
|
||||
create or replace function post_aggregates_comment_deleted()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF NEW.deleted = TRUE THEN
|
||||
update post_aggregates pa
|
||||
set comments = comments - 1
|
||||
where pa.post_id = NEW.post_id;
|
||||
ELSE
|
||||
update post_aggregates pa
|
||||
set comments = comments + 1
|
||||
where pa.post_id = NEW.post_id;
|
||||
END IF;
|
||||
return null;
|
||||
end $$;
|
||||
|
||||
create trigger post_aggregates_comment_set_deleted
|
||||
after update of deleted on comment
|
||||
for each row
|
||||
execute procedure post_aggregates_comment_deleted();
|
||||
|
||||
-- Fix issue with being able to necro-bump your own post
|
||||
create or replace function post_aggregates_comment_count()
|
||||
returns trigger language plpgsql
|
||||
as $$
|
||||
begin
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
update post_aggregates pa
|
||||
set comments = comments + 1,
|
||||
newest_comment_time = NEW.published
|
||||
where pa.post_id = NEW.post_id;
|
||||
|
||||
-- A 2 day necro-bump limit
|
||||
update post_aggregates pa
|
||||
set newest_comment_time_necro = NEW.published
|
||||
from post p
|
||||
where pa.post_id = p.id
|
||||
and pa.post_id = NEW.post_id
|
||||
-- Fix issue with being able to necro-bump your own post
|
||||
and NEW.creator_id != p.creator_id
|
||||
and pa.published > ('now'::timestamp - '2 days'::interval);
|
||||
|
||||
ELSIF (TG_OP = 'DELETE') THEN
|
||||
-- Join to post because that post may not exist anymore
|
||||
update post_aggregates pa
|
||||
set comments = comments - 1
|
||||
from post p
|
||||
where pa.post_id = p.id
|
||||
and pa.post_id = OLD.post_id;
|
||||
ELSIF (TG_OP = 'UPDATE') THEN
|
||||
-- Join to post because that post may not exist anymore
|
||||
update post_aggregates pa
|
||||
set comments = comments - 1
|
||||
from post p
|
||||
where pa.post_id = p.id
|
||||
and pa.post_id = OLD.post_id;
|
||||
END IF;
|
||||
return null;
|
||||
end $$;
|
Loading…
Reference in a new issue