Skip to content

Commit

Permalink
Support reporting messages
Browse files Browse the repository at this point in the history
  • Loading branch information
grishka committed Oct 26, 2023
1 parent 0a0ed79 commit bca463b
Show file tree
Hide file tree
Showing 17 changed files with 132 additions and 30 deletions.
4 changes: 2 additions & 2 deletions schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ CREATE TABLE `reports` (
`target_type` tinyint unsigned NOT NULL,
`content_type` tinyint unsigned DEFAULT NULL,
`target_id` int unsigned NOT NULL,
`content_id` int unsigned DEFAULT NULL,
`content_id` bigint unsigned DEFAULT NULL,
`comment` text NOT NULL,
`moderator_id` int unsigned DEFAULT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
Expand Down Expand Up @@ -627,4 +627,4 @@ CREATE TABLE `wall_posts` (

/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;

-- Dump completed on 2023-10-19 8:31:11
-- Dump completed on 2023-10-26 11:45:59
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package smithereen.activitypub.handlers;

import com.google.gson.JsonObject;

import java.net.URI;
import java.sql.SQLException;
import java.util.List;
Expand All @@ -10,6 +12,7 @@
import smithereen.activitypub.objects.ActivityPubObject;
import smithereen.activitypub.objects.Actor;
import smithereen.activitypub.objects.activities.Flag;
import smithereen.controllers.ObjectLinkResolver;
import smithereen.model.ForeignUser;
import smithereen.model.User;
import smithereen.exceptions.BadRequestException;
Expand All @@ -35,9 +38,9 @@ public void handle(ActivityHandlerContext context, Actor actor, Flag activity, A
throw new BadRequestException("Flag.object must contain at least one URI");

Actor reportedActor=null;
ActivityPubObject reportedContent=null;
List<ActivityPubObject> objects=activity.object.stream().map(uri->context.appContext.getObjectLinkResolver().resolve(uri, ActivityPubObject.class, false, false, false)).toList();
for(ActivityPubObject obj:objects){
Object reportedContent=null;
List<Object> objects=activity.object.stream().map(uri->context.appContext.getObjectLinkResolver().resolveNative(uri, Object.class, false, false, false, (JsonObject) null, true)).toList();
for(Object obj:objects){
if(obj instanceof Actor a){
if(reportedActor==null)
reportedActor=a;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/smithereen/controllers/MailController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -355,4 +356,14 @@ public PaginatedList<MailMessage> getHistory(User self, User peer, int offset, i
throw new InternalServerErrorException(x);
}
}

public Map<Long, MailMessage> getMessagesAsModerator(Collection<Long> ids){
if(ids.isEmpty())
return Map.of();
try{
return MailStorage.getMessagesAsModerator(ids);
}catch(SQLException x){
throw new InternalServerErrorException(x);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import smithereen.model.ForeignGroup;
import smithereen.model.ForeignUser;
import smithereen.model.Group;
import smithereen.model.MailMessage;
import smithereen.model.ObfuscatedObjectIDType;
import smithereen.model.PaginatedList;
import smithereen.model.Post;
import smithereen.model.Server;
Expand All @@ -29,6 +31,7 @@
import smithereen.exceptions.InternalServerErrorException;
import smithereen.exceptions.ObjectNotFoundException;
import smithereen.storage.ModerationStorage;
import smithereen.util.XTEA;

public class ModerationController{
private static final Logger LOG=LoggerFactory.getLogger(ModerationController.class);
Expand Down Expand Up @@ -60,7 +63,7 @@ private int createViolationReportInternal(@Nullable User self, Actor target, @Nu
ViolationReport.TargetType targetType;
ViolationReport.ContentType contentType;
int targetID;
int contentID;
long contentID;
if(target instanceof User u){
targetType=ViolationReport.TargetType.USER;
targetID=u.id;
Expand All @@ -74,6 +77,9 @@ private int createViolationReportInternal(@Nullable User self, Actor target, @Nu
if(content instanceof Post p){
contentType=ViolationReport.ContentType.POST;
contentID=p.id;
}else if(content instanceof MailMessage msg){
contentType=ViolationReport.ContentType.MESSAGE;
contentID=XTEA.deobfuscateObjectID(msg.id, ObfuscatedObjectIDType.MAIL_MESSAGE);
}else{
contentType=null;
contentID=0;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/smithereen/model/UserPermissions.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public boolean canReport(Object obj){
return managedGroups.getOrDefault(g.id, Group.AdminLevel.REGULAR)!=Group.AdminLevel.OWNER;
}else if(obj instanceof Post p){
return p.authorID!=userID;
}else if(obj instanceof MailMessage msg){
return msg.senderID!=userID;
}else{
return false;
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/smithereen/model/ViolationReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.Instant;

import smithereen.storage.DatabaseUtils;
import smithereen.util.XTEA;

public class ViolationReport{

Expand All @@ -13,7 +14,7 @@ public class ViolationReport{
public TargetType targetType;
public ContentType contentType;
public int targetID;
public int contentID;
public long contentID;
public String comment;
public int moderatorID;
public Instant time;
Expand All @@ -29,7 +30,9 @@ public static ViolationReport fromResultSet(ResultSet res) throws SQLException{
int contentType=res.getInt("content_type");
if(!res.wasNull()){
r.contentType=ContentType.values()[contentType];
r.contentID=res.getInt("content_id");
r.contentID=res.getLong("content_id");
if(r.contentType==ContentType.MESSAGE)
r.contentID=XTEA.obfuscateObjectID(r.contentID, ObfuscatedObjectIDType.MAIL_MESSAGE);
}
r.comment=res.getString("comment");
r.moderatorID=res.getInt("moderator_id");
Expand All @@ -40,7 +43,8 @@ public static ViolationReport fromResultSet(ResultSet res) throws SQLException{
}

public enum ContentType{
POST
POST,
MESSAGE
}

public enum TargetType{
Expand Down
28 changes: 23 additions & 5 deletions src/main/java/smithereen/routes/SettingsAdminRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import smithereen.Utils;
import smithereen.model.Account;
import smithereen.model.FederationRestriction;
import smithereen.model.MailMessage;
import smithereen.model.PaginatedList;
import smithereen.model.Post;
import smithereen.model.Server;
Expand Down Expand Up @@ -465,11 +466,28 @@ public static Object reportsList(Request req, Response resp, Account self, Appli
Set<Integer> userIDs=reports.list.stream().filter(r->r.targetType==ViolationReport.TargetType.USER).map(r->r.targetID).collect(Collectors.toSet());
userIDs.addAll(reports.list.stream().filter(r->r.reporterID!=0).map(r->r.reporterID).collect(Collectors.toSet()));
Set<Integer> groupIDs=reports.list.stream().filter(r->r.targetType==ViolationReport.TargetType.GROUP).map(r->r.targetID).collect(Collectors.toSet());
Set<Integer> postIDs=reports.list.stream().filter(r->r.contentType==ViolationReport.ContentType.POST).map(r->r.contentID).collect(Collectors.toSet());
Set<Integer> postIDs=reports.list.stream().filter(r->r.contentType==ViolationReport.ContentType.POST).map(r->(int)r.contentID).collect(Collectors.toSet());
Set<Long> messageIDs=reports.list.stream().filter(r->r.contentType==ViolationReport.ContentType.MESSAGE).map(r->r.contentID).collect(Collectors.toSet());

Map<Integer, Post> posts=ctx.getWallController().getPosts(postIDs);
Map<Long, MailMessage> messages=ctx.getMailController().getMessagesAsModerator(messageIDs);
for(Post post:posts.values()){
userIDs.add(post.authorID);
if(post.ownerID!=post.authorID){
if(post.ownerID>0)
userIDs.add(post.ownerID);
else
groupIDs.add(-post.ownerID);
}
}
for(MailMessage msg:messages.values()){
userIDs.add(msg.senderID);
}

model.with("users", ctx.getUsersController().getUsers(userIDs))
.with("groups", ctx.getGroupsController().getGroupsByIdAsMap(groupIDs))
.with("posts", ctx.getWallController().getPosts(postIDs));
.with("posts", posts)
.with("messages", messages);

return model;
}
Expand All @@ -486,7 +504,7 @@ public static Object reportAction(Request req, Response resp, Account self, Appl
}else if(req.queryParams("deleteContent")!=null){
// TODO notify user
if(report.contentType==ViolationReport.ContentType.POST){
Post post=ctx.getWallController().getPostOrThrow(report.contentID);
Post post=ctx.getWallController().getPostOrThrow((int)report.contentID);
ctx.getWallController().deletePostAsServerModerator(sessionInfo(req), post);
}else{
throw new BadRequestException();
Expand All @@ -498,7 +516,7 @@ public static Object reportAction(Request req, Response resp, Account self, Appl
resp.redirect(back(req));
}else if(req.queryParams("addCW")!=null){
if(report.contentType==ViolationReport.ContentType.POST){
Post post=ctx.getWallController().getPostOrThrow(report.contentID);
Post post=ctx.getWallController().getPostOrThrow((int)report.contentID);
if(post.hasContentWarning())
throw new BadRequestException();

Expand All @@ -519,7 +537,7 @@ public static Object reportAddCW(Request req, Response resp, Account self, Appli

// TODO notify user
if(report.contentType==ViolationReport.ContentType.POST){
Post post=ctx.getWallController().getPostOrThrow(report.contentID);
Post post=ctx.getWallController().getPostOrThrow((int)report.contentID);
ctx.getWallController().setPostCWAsModerator(sessionInfo(req).permissions, post, req.queryParams("cw"));
}else{
throw new BadRequestException();
Expand Down
31 changes: 28 additions & 3 deletions src/main/java/smithereen/routes/SystemRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import smithereen.model.ForeignGroup;
import smithereen.model.ForeignUser;
import smithereen.model.Group;
import smithereen.model.MailMessage;
import smithereen.model.OwnedContentObject;
import smithereen.model.Poll;
import smithereen.model.PollOption;
Expand Down Expand Up @@ -652,13 +653,14 @@ public static Object votePoll(Request req, Response resp, Account self, Applicat
public static Object reportForm(Request req, Response resp, Account self, ApplicationContext ctx){
requireQueryParams(req, "type", "id");
RenderedTemplateResponse model=new RenderedTemplateResponse("report_form", req);
int id=safeParseInt(req.queryParams("id"));
String rawID=req.queryParams("id");
Actor actorForAvatar;
String title, subtitle, boxTitle, textareaPlaceholder, titleText, otherServerDomain;
Lang l=lang(req);
String type=req.queryParams("type");
switch(type){
case "post" -> {
int id=safeParseInt(rawID);
Post post=ctx.getWallController().getPostOrThrow(id);
User postAuthor=ctx.getUsersController().getUserOrThrow(post.authorID);
actorForAvatar=postAuthor;
Expand All @@ -670,6 +672,7 @@ public static Object reportForm(Request req, Response resp, Account self, Applic
otherServerDomain=Config.isLocal(post.getActivityPubID()) ? null : post.getActivityPubID().getHost();
}
case "user" -> {
int id=safeParseInt(rawID);
User user=ctx.getUsersController().getUserOrThrow(id);
actorForAvatar=user;
title=user.getCompleteName();
Expand All @@ -680,6 +683,7 @@ public static Object reportForm(Request req, Response resp, Account self, Applic
otherServerDomain=user instanceof ForeignUser fu ? fu.domain : null;
}
case "group" -> {
int id=safeParseInt(rawID);
Group group=ctx.getGroupsController().getGroupOrThrow(id);
actorForAvatar=group;
title=group.name;
Expand All @@ -689,6 +693,18 @@ public static Object reportForm(Request req, Response resp, Account self, Applic
textareaPlaceholder=l.get("report_placeholder_profile");
otherServerDomain=group instanceof ForeignGroup fg ? fg.domain : null;
}
case "message" -> {
long id=decodeLong(rawID);
MailMessage msg=ctx.getMailController().getMessage(self.user, id, false);
User user=ctx.getUsersController().getUserOrThrow(msg.senderID);
actorForAvatar=user;
title=user.getCompleteName();
subtitle=truncateOnWordBoundary(msg.text, 200);
boxTitle=l.get("report_title_message");
titleText=l.get("report_text_message");
textareaPlaceholder=l.get("report_placeholder_content");
otherServerDomain=user instanceof ForeignUser fu ? fu.domain : null;
}
default -> throw new BadRequestException();
}
model.with("actorForAvatar", actorForAvatar)
Expand All @@ -697,12 +713,12 @@ public static Object reportForm(Request req, Response resp, Account self, Applic
.with("textAreaPlaceholder", textareaPlaceholder)
.with("reportTitleText", titleText)
.with("otherServerDomain", otherServerDomain);
return wrapForm(req, resp, "report_form", "/system/submitReport?type="+type+"&id="+id, boxTitle, "send", model);
return wrapForm(req, resp, "report_form", "/system/submitReport?type="+type+"&id="+rawID, boxTitle, "send", model);
}

public static Object submitReport(Request req, Response resp, Account self, ApplicationContext ctx){
requireQueryParams(req, "type", "id");
int id=safeParseInt(req.queryParams("id"));
String rawID=req.queryParams("id");
String type=req.queryParams("type");
String comment=req.queryParamOrDefault("reportText", "");
boolean forward="on".equals(req.queryParams("forward"));
Expand All @@ -712,18 +728,27 @@ public static Object submitReport(Request req, Response resp, Account self, Appl

switch(type){
case "post" -> {
int id=safeParseInt(rawID);
Post post=ctx.getWallController().getPostOrThrow(id);
content=post;
target=ctx.getUsersController().getUserOrThrow(post.authorID);
}
case "user" -> {
int id=safeParseInt(rawID);
target=ctx.getUsersController().getUserOrThrow(id);
content=null;
}
case "group" -> {
int id=safeParseInt(rawID);
target=ctx.getGroupsController().getGroupOrThrow(id);
content=null;
}
case "message" -> {
long id=decodeLong(rawID);
MailMessage msg=ctx.getMailController().getMessage(self.user, id, false);
target=ctx.getUsersController().getUserOrThrow(msg.senderID);
content=msg;
}
default -> throw new BadRequestException("invalid type");
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/smithereen/storage/DatabaseSchemaUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import smithereen.storage.sql.SQLQueryBuilder;

public class DatabaseSchemaUpdater{
public static final int SCHEMA_VERSION=31;
public static final int SCHEMA_VERSION=32;
private static final Logger LOG=LoggerFactory.getLogger(DatabaseSchemaUpdater.class);

public static void maybeUpdate() throws SQLException{
Expand Down Expand Up @@ -479,6 +479,7 @@ PRIMARY KEY (`owner_id`,`user_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;""");
}
case 31 -> conn.createStatement().execute("ALTER TABLE `wall_posts` ADD `privacy` tinyint unsigned NOT NULL DEFAULT '0'");
case 32 -> conn.createStatement().execute("ALTER TABLE reports CHANGE content_id content_id BIGINT UNSIGNED");
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/smithereen/storage/MailStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import smithereen.Utils;
Expand Down Expand Up @@ -256,4 +257,12 @@ public static PaginatedList<MailMessage> getHistory(int ownerID, int peerID, int
return new PaginatedList<>(msgs, total, offset, count);
}
}

public static Map<Long, MailMessage> getMessagesAsModerator(Collection<Long> ids) throws SQLException{
return new SQLQueryBuilder()
.selectFrom("mail_messages")
.whereIn("id", ids.stream().map(i->XTEA.deobfuscateObjectID(i, ObfuscatedObjectIDType.MAIL_MESSAGE)).collect(Collectors.toSet()))
.executeAsStream(MailMessage::fromResultSet)
.collect(Collectors.toMap(m->m.id, Function.identity()));
}
}
2 changes: 1 addition & 1 deletion src/main/java/smithereen/storage/ModerationStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import spark.utils.StringUtils;

public class ModerationStorage{
public static int createViolationReport(int reporterID, ViolationReport.TargetType targetType, int targetID, ViolationReport.ContentType contentType, int contentID, String comment, String otherServerDomain) throws SQLException{
public static int createViolationReport(int reporterID, ViolationReport.TargetType targetType, int targetID, ViolationReport.ContentType contentType, long contentID, String comment, String otherServerDomain) throws SQLException{
SQLQueryBuilder bldr=new SQLQueryBuilder()
.insertInto("reports")
.value("reporter_id", reporterID!=0 ? reporterID : null)
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/langs/en/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,6 @@
"gender_other": "other",
"remote_object_loading_error": "Failed to load the remote object.",
"err_access_user_content": "This user has chosen to hide this page.",
"menu_mail": "My Messages"
"menu_mail": "My Messages",
"content_type_message": "Message"
}
4 changes: 3 additions & 1 deletion src/main/resources/langs/en/reporting.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
"report_placeholder_content": "For example: spam",
"report_sent": "Report sent",
"report_forward_to_domain": "Anonymously forward to <b>{domain}</b>",
"report_submitted": "Your report was submitted to the server staff."
"report_submitted": "Your report was submitted to the server staff.",
"report_title_message": "Report a message",
"report_text_message": "What's wrong with this message?"
}
3 changes: 2 additions & 1 deletion src/main/resources/langs/ru/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,6 @@
"gender_other": "другой",
"remote_object_loading_error": "При получении объекта с другого сервера произошла ошибка.",
"err_access_user_content": "Пользователь предпочёл скрыть эту страницу.",
"menu_mail": "Мои Сообщения"
"menu_mail": "Мои Сообщения",
"content_type_message": "Сообщение"
}
Loading

0 comments on commit bca463b

Please sign in to comment.