3737public class AntiRevoke extends Feature {
3838
3939 private static final ConcurrentHashMap <String , Set <String >> messageRevokedMap = new ConcurrentHashMap <>();
40- private static final ThreadLocal <DateFormat > DATE_FORMAT_THREAD_LOCAL = ThreadLocal .withInitial (() ->
41- DateFormat .getDateTimeInstance (DateFormat .SHORT , DateFormat .SHORT , Utils .getApplication ().getResources ().getConfiguration ().getLocales ().get (0 )));
40+ private static final ThreadLocal <DateFormat > DATE_FORMAT_THREAD_LOCAL = ThreadLocal
41+ .withInitial (() -> DateFormat .getDateTimeInstance (DateFormat .SHORT , DateFormat .SHORT ,
42+ Utils .getApplication ().getResources ().getConfiguration ().getLocales ().get (0 )));
4243
4344 public AntiRevoke (ClassLoader loader , XSharedPreferences preferences ) {
4445 super (loader , preferences );
4546 }
4647
4748 @ Nullable
4849 private static Object findObjectFMessage (XC_MethodHook .MethodHookParam param ) throws IllegalAccessException {
49- if (param .args == null || param .args .length == 0 ) return null ;
50+ if (param .args == null || param .args .length == 0 )
51+ return null ;
5052
5153 if (FMessageWpp .TYPE .isInstance (param .args [0 ]))
5254 return param .args [0 ];
5355
5456 if (param .args .length > 1 ) {
5557 if (FMessageWpp .TYPE .isInstance (param .args [1 ]))
5658 return param .args [1 ];
57- var FMessageField = ReflectionUtils .findFieldUsingFilterIfExists (param .args [1 ].getClass (), f -> FMessageWpp .TYPE .isAssignableFrom (f .getType ()));
59+ var FMessageField = ReflectionUtils .findFieldUsingFilterIfExists (param .args [1 ].getClass (),
60+ f -> FMessageWpp .TYPE .isAssignableFrom (f .getType ()));
5861 if (FMessageField != null ) {
5962 return FMessageField .get (param .args [1 ]);
6063 }
6164 }
6265
63- var field = ReflectionUtils .findFieldUsingFilterIfExists (param .args [0 ].getClass (), f -> f .getType () == FMessageWpp .TYPE );
66+ var field = ReflectionUtils .findFieldUsingFilterIfExists (param .args [0 ].getClass (),
67+ f -> f .getType () == FMessageWpp .TYPE );
6468 if (field != null )
6569 return field .get (param .args [0 ]);
6670
67- var field1 = ReflectionUtils .findFieldUsingFilter (param .args [0 ].getClass (), f -> f .getType () == FMessageWpp .Key .TYPE );
71+ var field1 = ReflectionUtils .findFieldUsingFilter (param .args [0 ].getClass (),
72+ f -> f .getType () == FMessageWpp .Key .TYPE );
6873 if (field1 != null ) {
6974 var key = field1 .get (param .args [0 ]);
7075 return WppCore .getFMessageFromKey (key );
@@ -78,15 +83,18 @@ private static void persistRevokedMessage(FMessageWpp fMessage) {
7883 var stripJID = fMessage .getKey ().remoteJid .getPhoneNumber ();
7984 Set <String > messages = getRevokedMessagesForJid (fMessage );
8085 messages .add (messageKey );
81- DelMessageStore .getInstance (Utils .getApplication ()).insertMessage (stripJID , messageKey , System .currentTimeMillis ());
86+ DelMessageStore .getInstance (Utils .getApplication ()).insertMessage (stripJID , messageKey ,
87+ System .currentTimeMillis ());
8288 }
8389
8490 private static Set <String > getRevokedMessagesForJid (FMessageWpp fMessage ) {
8591 String stripJID = fMessage .getKey ().remoteJid .getPhoneNumber ();
86- if (stripJID == null ) return Collections .synchronizedSet (new java .util .HashSet <>());
92+ if (stripJID == null )
93+ return Collections .synchronizedSet (new java .util .HashSet <>());
8794 return messageRevokedMap .computeIfAbsent (stripJID , k -> {
8895 var messages = DelMessageStore .getInstance (Utils .getApplication ()).getMessagesByJid (k );
89- if (messages == null ) return Collections .synchronizedSet (new java .util .HashSet <>());
96+ if (messages == null )
97+ return Collections .synchronizedSet (new java .util .HashSet <>());
9098 return Collections .synchronizedSet (messages );
9199 });
92100 }
@@ -100,13 +108,14 @@ public void doHook() throws Exception {
100108 var unknownStatusPlaybackMethod = Unobfuscator .loadUnknownStatusPlaybackMethod (classLoader );
101109 logDebug (Unobfuscator .getMethodDescriptor (unknownStatusPlaybackMethod ));
102110
103- var statusPlaybackClass = Unobfuscator .loadStatusPlaybackViewClass (classLoader );
111+ Class <?> statusPlaybackClass = Unobfuscator .loadStatusPlaybackViewClass (classLoader );
104112 logDebug (statusPlaybackClass );
105113
106114 XposedBridge .hookMethod (antiRevokeMessageMethod , new XC_MethodHook () {
107115 @ Override
108116 protected void beforeHookedMethod (MethodHookParam param ) throws Exception {
109- if (param .args == null || param .args .length == 0 || param .args [0 ] == null ) return ;
117+ if (param .args == null || param .args .length == 0 || param .args [0 ] == null )
118+ return ;
110119
111120 var fMessage = new FMessageWpp (param .args [0 ]);
112121 var messageKey = fMessage .getKey ();
@@ -116,14 +125,20 @@ protected void beforeHookedMethod(MethodHookParam param) throws Exception {
116125 if (WppCore .getPrivBoolean (messageID + "_delpass" , false )) {
117126 WppCore .removePrivKey (messageID + "_delpass" );
118127 var activity = WppCore .getCurrentActivity ();
119- Class <?> StatusPlaybackActivityClass = classLoader .loadClass ("com.whatsapp.status.playback.StatusPlaybackActivity" );
128+ Class <?> StatusPlaybackActivityClass = classLoader
129+ .loadClass ("com.whatsapp.status.playback.StatusPlaybackActivity" );
120130 if (activity != null && StatusPlaybackActivityClass .isInstance (activity )) {
121131 activity .finish ();
122132 }
123133 return ;
124134 }
135+ // For group messages: intercept any non-self revocation regardless of
136+ // deviceJid.
137+ // Previously the deviceJid != null guard caused all group revocations where
138+ // deviceJid is null (the common case for other participants) to be silently
139+ // skipped.
125140 if (messageKey .remoteJid .isGroup ()) {
126- if (deviceJid != null && handleRevocationAttempt (fMessage ) != 0 ) {
141+ if (! messageKey . isFromMe && handleRevocationAttempt (fMessage ) != 0 ) {
127142 param .setResult (true );
128143 }
129144 } else if (!messageKey .isFromMe && handleRevocationAttempt (fMessage ) != 0 ) {
@@ -132,11 +147,11 @@ protected void beforeHookedMethod(MethodHookParam param) throws Exception {
132147 }
133148 });
134149
135-
136150 ConversationItemListener .conversationListeners .add (new ConversationItemListener .OnConversationItemListener () {
137151 @ Override
138152 public void onItemBind (FMessageWpp fMessage , ViewGroup viewGroup ) {
139- if (fMessage .getKey ().isFromMe ) return ;
153+ if (fMessage .getKey ().isFromMe )
154+ return ;
140155 var dateTextView = (TextView ) viewGroup .findViewById (Utils .getID ("date" , "id" ));
141156 bindRevokedMessageUI (fMessage , dateTextView , "antirevoke" );
142157 }
@@ -149,10 +164,12 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
149164 var objFMessage = findObjectFMessage (param );
150165 var field = ReflectionUtils .getFieldByType (param .method .getDeclaringClass (), statusPlaybackClass );
151166
152- if (obj == null || field == null || objFMessage == null ) return ;
167+ if (obj == null || field == null || objFMessage == null )
168+ return ;
153169
154170 Object objView = field .get (obj );
155- if (objView == null ) return ;
171+ if (objView == null )
172+ return ;
156173
157174 var textViews = ReflectionUtils .getFieldsByType (statusPlaybackClass , TextView .class );
158175 if (textViews .isEmpty ()) {
@@ -173,22 +190,29 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
173190 }
174191
175192 private void bindRevokedMessageUI (FMessageWpp fMessage , TextView dateTextView , String antirevokeType ) {
176- if (dateTextView == null ) return ;
193+ if (dateTextView == null )
194+ return ;
177195
178196 var key = fMessage .getKey ();
179197 var messageRevokedList = getRevokedMessagesForJid (fMessage );
180198 var id = fMessage .getRowId ();
181199 String keyOrig = null ;
182- if (messageRevokedList .contains (key .messageID ) || ((keyOrig = MessageStore .getInstance ().getOriginalMessageKey (id )) != null && messageRevokedList .contains (keyOrig ))) {
183- var timestamp = DelMessageStore .getInstance (Utils .getApplication ()).getTimestampByMessageId (keyOrig == null ? key .messageID : keyOrig );
200+ if (messageRevokedList .contains (key .messageID )
201+ || ((keyOrig = MessageStore .getInstance ().getOriginalMessageKey (id )) != null
202+ && messageRevokedList .contains (keyOrig ))) {
203+ var timestamp = DelMessageStore .getInstance (Utils .getApplication ())
204+ .getTimestampByMessageId (keyOrig == null ? key .messageID : keyOrig );
184205 if (timestamp > 0 ) {
185206 var date = Objects .requireNonNull (DATE_FORMAT_THREAD_LOCAL .get ()).format (new Date (timestamp ));
186207 dateTextView .getPaint ().setUnderlineText (true );
187- dateTextView .setOnClickListener (v -> Utils .showToast (String .format (Utils .getApplication ().getString (ResId .string .message_removed_on ), date ), Toast .LENGTH_LONG ));
208+ dateTextView .setOnClickListener (v -> Utils .showToast (
209+ String .format (Utils .getApplication ().getString (ResId .string .message_removed_on ), date ),
210+ Toast .LENGTH_LONG ));
188211 }
189212 var antirevokeValue = Integer .parseInt (prefs .getString (antirevokeType , "0" ));
190213 if (antirevokeValue == 1 ) {
191- var newTextData = UnobfuscatorCache .getInstance ().getString ("messagedeleted" ) + " | " + dateTextView .getText ();
214+ var newTextData = UnobfuscatorCache .getInstance ().getString ("messagedeleted" ) + " | "
215+ + dateTextView .getText ();
192216 dateTextView .setText (newTextData );
193217 } else if (antirevokeValue == 2 ) {
194218 var drawable = Utils .getApplication ().getDrawable (ResId .drawable .deleted );
@@ -207,7 +231,6 @@ private void bindRevokedMessageUI(FMessageWpp fMessage, TextView dateTextView, S
207231 }
208232 }
209233
210-
211234 private int handleRevocationAttempt (FMessageWpp fMessage ) {
212235 try {
213236 showRevocationToast (fMessage );
@@ -216,21 +239,25 @@ private int handleRevocationAttempt(FMessageWpp fMessage) {
216239 }
217240 String messageKey = (String ) XposedHelpers .getObjectField (fMessage .getObject (), "A01" );
218241 String stripJID = fMessage .getKey ().remoteJid .getPhoneNumber ();
219- int revokeboolean = stripJID .equals ("status" ) ? Integer .parseInt (prefs .getString ("antirevokestatus" , "0" )) : Integer .parseInt (prefs .getString ("antirevoke" , "0" ));
220- if (revokeboolean == 0 ) return revokeboolean ;
242+ int revokeboolean = stripJID .equals ("status" ) ? Integer .parseInt (prefs .getString ("antirevokestatus" , "0" ))
243+ : Integer .parseInt (prefs .getString ("antirevoke" , "0" ));
244+ if (revokeboolean == 0 )
245+ return revokeboolean ;
221246 var messageRevokedList = getRevokedMessagesForJid (fMessage );
222247 if (!messageRevokedList .contains (messageKey )) {
223248 try {
224249 CompletableFuture .runAsync (() -> {
225250 persistRevokedMessage (fMessage );
226251 try {
227252 var mConversation = WppCore .getCurrentConversation ();
228- if (mConversation != null && Objects .equals (stripJID , WppCore .getCurrentUserJid ().getPhoneNumber ())) {
253+ if (mConversation != null
254+ && Objects .equals (stripJID , WppCore .getCurrentUserJid ().getPhoneNumber ())) {
229255 mConversation .runOnUiThread (() -> {
230256 if (mConversation .hasWindowFocus ()) {
231257 mConversation .startActivity (mConversation .getIntent ());
232258 mConversation .overridePendingTransition (0 , 0 );
233- mConversation .getWindow ().getDecorView ().findViewById (android .R .id .content ).postInvalidate ();
259+ mConversation .getWindow ().getDecorView ().findViewById (android .R .id .content )
260+ .postInvalidate ();
234261 } else {
235262 mConversation .recreate ();
236263 }
@@ -254,13 +281,20 @@ private void showRevocationToast(FMessageWpp fMessage) {
254281 messageSuffix = Utils .getApplication ().getString (ResId .string .deleted_status );
255282 jidAuthor = fMessage .getUserJid ();
256283 }
257- if (jidAuthor .userJid == null ) return ;
284+ if (jidAuthor .userJid == null )
285+ return ;
258286 String name = WppCore .getContactName (jidAuthor );
259287 if (TextUtils .isEmpty (name )) {
260288 name = jidAuthor .getPhoneNumber ();
261289 }
262290 String message ;
263- if (jidAuthor .isGroup () && fMessage .getUserJid ().isNull ()) {
291+ // Show "Participant deleted a message in GroupName" for group messages where we
292+ // know
293+ // who the sender is (!isNull). The old condition was inverted — it showed this
294+ // only
295+ // when getUserJid().isNull() which is exactly when we do NOT know the
296+ // participant.
297+ if (jidAuthor .isGroup () && !fMessage .getUserJid ().isNull ()) {
264298 var participantJid = fMessage .getUserJid ();
265299 String participantName = WppCore .getContactName (participantJid );
266300 if (TextUtils .isEmpty (participantName )) {
@@ -273,7 +307,8 @@ private void showRevocationToast(FMessageWpp fMessage) {
273307 if (prefs .getBoolean ("toastdeleted" , false )) {
274308 Utils .showToast (message , Toast .LENGTH_LONG );
275309 }
276- Tasker .sendTaskerEvent (name , jidAuthor .getPhoneNumber (), jidAuthor .isStatus () ? "deleted_status" : "deleted_message" );
310+ Tasker .sendTaskerEvent (name , jidAuthor .getPhoneNumber (),
311+ jidAuthor .isStatus () ? "deleted_status" : "deleted_message" );
277312 }
278313
279314 @ NonNull
0 commit comments