Skip to content

Commit

Permalink
Implement XEP-0425 Moderated Message Retraction
Browse files Browse the repository at this point in the history
  • Loading branch information
tmolitor-stud-tu committed Apr 2, 2024
1 parent 2eb6593 commit 7ee93f8
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 18 deletions.
3 changes: 2 additions & 1 deletion Monal/Classes/DataLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ extern NSString* const kMessageTypeFiletransfer;
-(void) deleteMessageHistoryLocally:(NSNumber*) messageNo;
-(void) updateMessageHistory:(NSNumber*) messageNo withText:(NSString*) newText;
-(NSNumber* _Nullable) getLMCHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo;
-(NSNumber* _Nullable) getRetractionHistoryIDForMessageId:(NSString* _Nullable) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo;
-(NSNumber* _Nullable) getRetractionHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo;
-(NSNumber* _Nullable) getRetractionHistoryIDForModeratedStanzaId:(NSString*) stanzaId from:(NSString*) from andAccount:(NSNumber*) accountNo;

-(NSDate* _Nullable) returnTimestampForQuote:(NSNumber*) historyID;
-(BOOL) checkLMCEligible:(NSNumber*) historyID encrypted:(BOOL) encrypted historyBaseID:(NSNumber* _Nullable) historyBaseID;
Expand Down
11 changes: 10 additions & 1 deletion Monal/Classes/DataLayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -1524,7 +1524,7 @@ -(NSNumber* _Nullable) getLMCHistoryIDForMessageId:(NSString*) messageid from:(N
}];
}

-(NSNumber* _Nullable) getRetractionHistoryIDForMessageId:(NSString* _Nullable) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo
-(NSNumber* _Nullable) getRetractionHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo
{
return [self.db idReadTransaction:^{
return [self.db executeScalar:@"SELECT M.message_history_id FROM message_history AS M INNER JOIN account AS A ON M.account_id=A.account_id INNER JOIN buddylist AS B on M.buddy_name = B.buddy_name AND M.account_id = B.account_id WHERE M.account_id=? AND ( \
Expand All @@ -1534,6 +1534,15 @@ -(NSNumber* _Nullable) getRetractionHistoryIDForMessageId:(NSString* _Nullable)
}];
}

-(NSNumber* _Nullable) getRetractionHistoryIDForModeratedStanzaId:(NSString*) stanzaId from:(NSString*) from andAccount:(NSNumber*) accountNo
{
return [self.db idReadTransaction:^{
return [self.db executeScalar:@"SELECT M.message_history_id FROM message_history AS M INNER JOIN account AS A ON M.account_id=A.account_id INNER JOIN buddylist AS B on M.buddy_name = B.buddy_name AND M.account_id = B.account_id \
WHERE M.account_id=? AND B.Muc=1 AND M.stanzaid=? AND M.buddy_name=?;"
andArguments:@[accountNo, stanzaId, from]];
}];
}

-(NSDate* _Nullable) returnTimestampForQuote:(NSNumber*) historyID
{
return [self.db idReadTransaction:^{
Expand Down
27 changes: 27 additions & 0 deletions Monal/Classes/MLIQProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,33 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode
userInfo:@{@"versionInfo": newSoftwareVersionInfo}];
$$

$$class_handler(handleModerationResponse, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(MLMessage*, msg))
[msg updateWithMessage:[[DataLayer sharedInstance] messageForHistoryID:msg.messageDBId]]; //make sure our msg is up to date
if([iqNode check:@"/<type=error>"])
{
DDLogError(@"Moderating message %@ returned an error: %@", msg, [iqNode findFirst:@"error"]);
[HelperTools postError:[NSString stringWithFormat:NSLocalizedString(@"Failed to moderate message in group/channel '%@'", @""), iqNode.fromUser] withNode:iqNode andAccount:account andIsSevere:YES];
return;
}

DDLogInfo(@"Successfully moderated message in muc: %@", msg);
[[DataLayer sharedInstance] deleteMessageHistory:msg.messageDBId];

//update ui
DDLogInfo(@"Sending out kMonalDeletedMessageNotice notification for historyId %@", msg.messageDBId);
[[MLNotificationQueue currentQueue] postNotificationName:kMonalDeletedMessageNotice object:account userInfo:@{
@"message": msg,
@"historyId": msg.messageDBId,
@"contact": msg.contact,
}];

//update unread count in active chats list
[msg.contact updateUnreadCount];
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{
@"contact": msg.contact,
}];
$$

+(void) respondWithErrorTo:(XMPPIQ*) iqNode onAccount:(xmpp*) account
{
XMPPIQ* errorIq = [[XMPPIQ alloc] initAsErrorTo:iqNode];
Expand Down
12 changes: 10 additions & 2 deletions Monal/Classes/MLMessageProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,16 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag
if([messageNode check:@"{urn:xmpp:message-retract:1}retract"])
{
NSString* idToRetract = [messageNode findFirst:@"{urn:xmpp:message-retract:1}retract@id"];
//this checks if this message is from the same jid as the message it tries to retract (e.g. inbound can only retract inbound and outbound only outbound)
NSNumber* historyIdToRetract = [[DataLayer sharedInstance] getRetractionHistoryIDForMessageId:idToRetract from:messageNode.fromUser actualFrom:actualFrom participantJid:participantJid andAccount:account.accountNo];
NSNumber* historyIdToRetract = nil;
if(possiblyUnknownContact.isGroup && [[account.mucProcessor getRoomFeaturesForMuc:possiblyUnknownContact.contactJid] containsObject:@"urn:xmpp:message-moderate:1"] && [messageNode findFirst:@"{urn:xmpp:message-retract:1}retract/{urn:xmpp:message-moderate:1}moderated"])
{
historyIdToRetract = [[DataLayer sharedInstance] getRetractionHistoryIDForModeratedStanzaId:idToRetract from:messageNode.fromUser andAccount:account.accountNo];
}
else
{
//this checks for all spelled out in the business rules of XEP-0424
historyIdToRetract = [[DataLayer sharedInstance] getRetractionHistoryIDForMessageId:idToRetract from:messageNode.fromUser actualFrom:actualFrom participantJid:participantJid andAccount:account.accountNo];
}

if(historyIdToRetract != nil)
{
Expand Down
35 changes: 22 additions & 13 deletions Monal/Classes/chatViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -2483,17 +2483,26 @@ -(UISwipeActionsConfiguration*) tableView:(UITableView*) tableView trailingSwipe
quoteAction.image = [[[UIImage systemImageNamed:@"quote.bubble.fill"] imageWithHorizontallyFlippedOrientation] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAutomatic];

UIContextualAction* retractAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Retract", @"Chat msg action") handler:^(UIContextualAction* action, UIView* sourceView, void (^completionHandler)(BOOL actionPerformed)) {
[self.xmppAccount retractMessage:message];
[[DataLayer sharedInstance] deleteMessageHistory:message.messageDBId];
[message updateWithMessage:[[[DataLayer sharedInstance] messagesForHistoryIDs:@[message.messageDBId]] firstObject]];
//only delete directly if we sent that message, try to moderate otherwise
if(!message.inbound)
{
[self.xmppAccount retractMessage:message];
[[DataLayer sharedInstance] deleteMessageHistory:message.messageDBId];
[message updateWithMessage:[[[DataLayer sharedInstance] messagesForHistoryIDs:@[message.messageDBId]] firstObject]];

//update table entry
[self->_messageTable beginUpdates];
[self->_messageTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self->_messageTable endUpdates];

//update active chats if necessary
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}];
//update table entry
[self->_messageTable beginUpdates];
[self->_messageTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self->_messageTable endUpdates];

//update active chats if necessary
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}];
}
else
{
//hardcode reason for now (change this when rewriting chatui using swiftui)
[self.xmppAccount moderateMessage:message withReason:@"This message contains inappropriate content for this forum."];
}

return completionHandler(YES);
}];
Expand All @@ -2510,7 +2519,7 @@ -(UISwipeActionsConfiguration*) tableView:(UITableView*) tableView trailingSwipe

//update active chats if necessary
[[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}];

return completionHandler(YES);
}];
localDeleteAction.backgroundColor = UIColor.systemYellowColor;
Expand Down Expand Up @@ -2538,8 +2547,8 @@ -(UISwipeActionsConfiguration*) tableView:(UITableView*) tableView trailingSwipe
LMCEditAction,
retractAction,
]];
//only allow retraction for outgoing messages
else if(!message.inbound)
//only allow retraction for outgoing messages or if we are the moderator of that muc
else if(!message.inbound || (self.contact.isGroup && [[[DataLayer sharedInstance] getOwnRoleInGroupOrChannel:self.contact] isEqualToString:@"moderator"] && [[self.xmppAccount.mucProcessor getRoomFeaturesForMuc:self.contact.contactJid] containsObject:@"urn:xmpp:message-moderate:1"]))
return [UISwipeActionsConfiguration configurationWithActions:@[
quoteAction,
copyAction,
Expand Down
1 change: 1 addition & 0 deletions Monal/Classes/xmpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ typedef void (^monal_iq_handler_t)(XMPPIQ* _Nullable);
send a message to a contact with xmpp id
*/
-(void) retractMessage:(MLMessage*) msg;
-(void) moderateMessage:(MLMessage*) msg withReason:(NSString*) reason;
-(void) sendMessage:(NSString*) message toContact:(MLContact*) contact isEncrypted:(BOOL) encrypt isUpload:(BOOL) isUpload andMessageId:(NSString*) messageId;
-(void) sendMessage:(NSString*) message toContact:(MLContact*) contact isEncrypted:(BOOL) encrypt isUpload:(BOOL) isUpload andMessageId:(NSString*) messageId withLMCId:(NSString* _Nullable) LMCId;
-(void) sendChatState:(BOOL) isTyping toContact:(nonnull MLContact*) contact;
Expand Down
14 changes: 14 additions & 0 deletions Monal/Classes/xmpp.m
Original file line number Diff line number Diff line change
Expand Up @@ -3317,6 +3317,20 @@ -(void) retractMessage:(MLMessage*) msg
[self send:messageNode];
}

-(void) moderateMessage:(MLMessage*) msg withReason:(NSString*) reason
{
MLAssert(msg.isMuc, @"Moderated message must be in a muc!");

XMPPIQ* iqNode = [[XMPPIQ alloc] initWithType:kiqSetType to:msg.buddyName];
[iqNode addChildNode:[[MLXMLNode alloc] initWithElement:@"moderate" andNamespace:@"urn:xmpp:message-moderate:1" withAttributes:@{
@"id": msg.stanzaId,
} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"retract" andNamespace:@"urn:xmpp:message-retract:1"],
[[MLXMLNode alloc] initWithElement:@"reason" andData:reason],
] andData:nil]];
[self sendIq:iqNode withHandler:$newHandler(MLIQProcessor, handleModerationResponse, $ID(msg))];
}

-(void) addEME:(NSString*) encryptionNamesapce withName:(NSString* _Nullable) name toMessageNode:(XMPPMessage*) messageNode
{
if(name)
Expand Down
11 changes: 10 additions & 1 deletion monal.doap
Original file line number Diff line number Diff line change
Expand Up @@ -654,10 +654,19 @@
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0424.html" />
<xmpp:status>complete</xmpp:status>
<xmpp:since>6.3</xmpp:since>
<xmpp:version>0.4.0</xmpp:version>
<xmpp:version>0.4.1</xmpp:version>
<xmpp:note>XEP-0424: Message Retraction</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0425.html" />
<xmpp:status>complete</xmpp:status>
<xmpp:since>6.3</xmpp:since>
<xmpp:version>0.3.0</xmpp:version>
<xmpp:note>XEP-0425: Moderated Message Retraction</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0440.html" />
Expand Down

0 comments on commit 7ee93f8

Please sign in to comment.