replyKey;
if(parent!=null){
- replyKey=new int[parent.replyKey.length+1];
- System.arraycopy(parent.replyKey, 0, replyKey, 0, parent.replyKey.length);
- replyKey[replyKey.length-1]=parent.id;
+ replyKey=parent.getReplyKeyForReplies();
Post topLevel;
- if(parent.replyKey.length>0){
- topLevel=PostStorage.getPostByID(parent.replyKey[0], false);
- if(topLevel!=null && !mentionedUsers.contains(topLevel.user))
- mentionedUsers.add(topLevel.user);
+ if(parent.replyKey.size()>0){
+ topLevel=getPostOrThrow(parent.replyKey.get(0));
}else{
topLevel=parent;
}
- if(topLevel!=null){
- if(topLevel.isGroupOwner()){
- ownerGroupID=((Group) topLevel.owner).id;
- ownerUserID=0;
- ensureUserNotBlocked(author, (Group) topLevel.owner);
- isTopLevelPostOwn=false;
- }else{
- ownerGroupID=0;
- ownerUserID=((User) topLevel.owner).id;
- ensureUserNotBlocked(author, (User)topLevel.owner);
- isTopLevelPostOwn=ownerUserID==topLevel.user.id;
- }
+
+ OwnerAndAuthor topLevelOwnership=getContentAuthorAndOwner(topLevel);
+ Actor topLevelOwner=topLevelOwnership.owner();
+ User topLevelAuthor=topLevelOwnership.author();
+
+ if(!mentionedUsers.contains(topLevelAuthor))
+ mentionedUsers.add(topLevelAuthor);
+ if(topLevel.isGroupOwner()){
+ ownerGroupID=-topLevel.ownerID;
+ ownerUserID=0;
+ ensureUserNotBlocked(author, (Group)topLevelOwner);
+ isTopLevelPostOwn=false;
+ }else{
+ ownerGroupID=0;
+ ownerUserID=topLevel.ownerID;
+ ensureUserNotBlocked(author, (User)topLevelOwner);
+ isTopLevelPostOwn=ownerUserID==topLevel.authorID;
+ context.getPrivacyController().enforceUserPrivacy(author, (User)topLevelOwner, UserPrivacySettingKey.WALL_COMMENTING);
}
}else{
replyKey=null;
@@ -278,12 +300,13 @@ public User resolveMention(String uri){
if(parent!=null){
// comment replies start with mentions, but only if it's a reply to a comment, not a top-level post
- if(parent.replyKey.length>0 && text.startsWith(""+escapeHTML(parent.user.getNameForReply())+", ")){
- text="
"
- +escapeHTML(parent.user.getNameForReply())+""+text.substring(parent.user.getNameForReply().length()+3);
+ User parentAuthor=context.getUsersController().getUserOrThrow(parent.authorID);
+ if(parent.replyKey.size()>0 && text.startsWith("
"+escapeHTML(parentAuthor.getNameForReply())+", ")){
+ text="
"
+ +escapeHTML(parentAuthor.getNameForReply())+""+text.substring(parentAuthor.getNameForReply().length()+3);
}
- if(!mentionedUsers.contains(parent.user))
- mentionedUsers.add(parent.user);
+ if(!mentionedUsers.contains(parentAuthor))
+ mentionedUsers.add(parentAuthor);
}
return text;
@@ -312,11 +335,23 @@ public Post getPostOrThrow(int id){
@NotNull
public Post getLocalPostOrThrow(int id){
Post post=getPostOrThrow(id);
- if(!post.local)
+ if(!post.isLocal())
throw new ObjectNotFoundException("err_post_not_found");
return post;
}
+ @NotNull
+ public Post getPostOrThrow(URI apID){
+ try{
+ Post post=PostStorage.getPostByID(apID);
+ if(post==null)
+ throw new ObjectNotFoundException();
+ return post;
+ }catch(SQLException x){
+ throw new InternalServerErrorException(x);
+ }
+ }
+
@NotNull
public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, int id, @NotNull String textSource, @Nullable String contentWarning, @NotNull List attachmentIDs, @Nullable Poll poll){
try{
@@ -333,9 +368,9 @@ public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, i
int pollID=0;
if(poll!=null && !Objects.equals(post.poll, poll)){
- List opts=poll.options.stream().map(o->o.name).collect(Collectors.toList());
+ List opts=poll.options.stream().map(o->o.text).collect(Collectors.toList());
if(opts.size()>=2){
- pollID=PostStorage.createPoll(post.owner.getOwnerID(), poll.question, opts, poll.anonymous, poll.multipleChoice, poll.endTime);
+ pollID=PostStorage.createPoll(post.ownerID, poll.question, opts, poll.anonymous, poll.multipleChoice, poll.endTime);
}
}
if(post.poll!=null && pollID==0){
@@ -349,8 +384,8 @@ public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, i
ArrayList attachObjects=new ArrayList<>();
ArrayList remainingAttachments=new ArrayList<>(attachmentIDs);
- if(post.attachment!=null){
- for(ActivityPubObject att:post.attachment){
+ if(post.attachments!=null){
+ for(ActivityPubObject att:post.attachments){
if(att instanceof LocalImage li){
if(!remainingAttachments.remove(li.localID)){
LOG.debug("Deleting attachment: {}", li.localID);
@@ -368,7 +403,7 @@ public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, i
for(String aid : remainingAttachments){
if(!aid.matches("^[a-fA-F0-9]{32}$"))
continue;
- ActivityPubObject obj=MediaCache.getAndDeleteDraftAttachment(aid, post.user.id);
+ ActivityPubObject obj=MediaCache.getAndDeleteDraftAttachment(aid, post.authorID, "post_media");
if(obj!=null){
attachObjects.add(obj);
attachmentCount++;
@@ -391,7 +426,7 @@ public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, i
}
PostStorage.updateWallPost(id, text, textSource, mentionedUsers, attachments, contentWarning, pollID);
- if(!post.isGroupOwner() && post.owner.getLocalID()==post.user.id){
+ if(post.ownerID>0 && post.ownerID==post.authorID){
context.getNewsfeedController().clearFriendsFeedCache();
}
@@ -411,10 +446,25 @@ public Post editPost(@NotNull User self, @NotNull UserPermissions permissions, i
* @param count Maximum number of posts to return
* @return A reverse-chronologically sorted paginated list of wall posts
*/
- public PaginatedList getWallPosts(@NotNull Actor owner, boolean ownOnly, int offset, int count){
+ public PaginatedList getWallPosts(@Nullable User self, @NotNull Actor owner, boolean ownOnly, int offset, int count){
try{
int[] postCount={0};
- List wall=PostStorage.getWallPosts(owner.getLocalID(), owner instanceof Group, 0, 0, offset, count, postCount, ownOnly);
+ Set allowedPrivacy;
+ if(self!=null && owner instanceof User ownerUser){
+ if(self.id==owner.getOwnerID()){
+ allowedPrivacy=EnumSet.allOf(Post.Privacy.class);
+ }else{
+ FriendshipStatus status=context.getFriendsController().getSimpleFriendshipStatus(self, ownerUser);
+ allowedPrivacy=switch(status){
+ case FOLLOWING -> EnumSet.of(Post.Privacy.PUBLIC, Post.Privacy.FOLLOWERS_ONLY, Post.Privacy.FOLLOWERS_AND_MENTIONED);
+ case FRIENDS -> EnumSet.of(Post.Privacy.PUBLIC, Post.Privacy.FOLLOWERS_ONLY, Post.Privacy.FOLLOWERS_AND_MENTIONED, Post.Privacy.FRIENDS_ONLY);
+ default -> EnumSet.of(Post.Privacy.PUBLIC);
+ };
+ }
+ }else{
+ allowedPrivacy=EnumSet.of(Post.Privacy.PUBLIC);
+ }
+ List wall=PostStorage.getWallPosts(owner.getLocalID(), owner instanceof Group, 0, 0, offset, count, postCount, ownOnly, allowedPrivacy);
return new PaginatedList<>(wall, postCount[0], offset, count);
}catch(SQLException x){
throw new InternalServerErrorException(x);
@@ -429,8 +479,10 @@ public PaginatedList getWallPosts(@NotNull Actor owner, boolean ownOnly, i
* @param count Maximum number of posts to return
* @return A reverse-chronologically sorted paginated list of wall posts
*/
- public PaginatedList getWallToWallPosts(@NotNull User user, @NotNull User otherUser, int offset, int count){
+ public PaginatedList getWallToWallPosts(@Nullable User self, @NotNull User user, @NotNull User otherUser, int offset, int count){
try{
+ context.getPrivacyController().enforceUserPrivacy(self, user, UserPrivacySettingKey.WALL_OTHERS_POSTS);
+ context.getPrivacyController().enforceUserPrivacy(self, otherUser, UserPrivacySettingKey.WALL_OTHERS_POSTS);
int[] postCount={0};
List wall=PostStorage.getWallToWall(user.id, otherUser.id, offset, count, postCount);
return new PaginatedList<>(wall, postCount[0], offset, count);
@@ -443,15 +495,18 @@ public PaginatedList getWallToWallPosts(@NotNull User user, @NotNull User
* Add top-level comments to each post.
* @param posts List of posts to add comments to
*/
- public void populateCommentPreviews(@NotNull List posts){
+ public void populateCommentPreviews(@Nullable User self, @NotNull List posts){
try{
- Set postIDs=posts.stream().map((Post p)->p.id).collect(Collectors.toSet());
+ Set postIDs=posts.stream().map(p->p.post.id).collect(Collectors.toSet());
Map> allComments=PostStorage.getRepliesForFeed(postIDs);
- for(Post post:posts){
- PaginatedList comments=allComments.get(post.id);
+ for(PostViewModel post:posts){
+ PaginatedList comments=allComments.get(post.post.id);
if(comments!=null){
- post.repliesObjects=comments.list;
- post.totalTopLevelComments=comments.total;
+ context.getPrivacyController().filterPosts(self, comments.list);
+ if(!comments.list.isEmpty()){
+ post.repliesObjects=comments.list.stream().map(PostViewModel::new).toList();
+ post.totalTopLevelComments=comments.total;
+ }
}
}
}catch(SQLException x){
@@ -465,11 +520,21 @@ public void populateCommentPreviews(@NotNull List posts){
* @param self Current user to check whether posts are liked
* @return A map from a post ID to a {@link UserInteractions} object for each post
*/
- public Map getUserInteractions(@NotNull List posts, @Nullable User self){
+ public Map getUserInteractions(@NotNull List posts, @Nullable User self){
try{
- Set postIDs=posts.stream().map((Post p)->p.id).collect(Collectors.toSet());
- for(Post p:posts){
+ Set postIDs=posts.stream().map(p->p.post.id).collect(Collectors.toSet());
+ Set ownerUserIDs=new HashSet<>();
+ for(PostViewModel p:posts){
p.getAllReplyIDs(postIDs);
+ if(p.post.ownerID>0)
+ ownerUserIDs.add(p.post.ownerID);
+ }
+ Map canComment=context.getUsersController().getUsers(ownerUserIDs)
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e->context.getPrivacyController().checkUserPrivacy(self, e.getValue(), UserPrivacySettingKey.WALL_COMMENTING)));
+ for(PostViewModel post:posts){
+ post.canComment=canComment.getOrDefault(post.post.ownerID, true);
}
return PostStorage.getPostInteractions(postIDs, self!=null ? self.id : 0);
}catch(SQLException x){
@@ -480,7 +545,7 @@ public Map getUserInteractions(@NotNull List po
public void sendUpdateQuestionIfNeeded(Post post){
if(post.poll==null)
throw new IllegalArgumentException("Post must have a poll");
- if(!post.local)
+ if(!post.isLocal())
return;
if(post.poll.lastVoteTime.until(Instant.now(), ChronoUnit.MINUTES)>=5){
@@ -513,17 +578,43 @@ public Map