From 2687f99cfb2d2178da3ef3180c47d355f3506ac6 Mon Sep 17 00:00:00 2001 From: faroukbmiled Date: Sun, 5 Apr 2026 11:01:58 +0100 Subject: [PATCH] - Read receipt mode setting: button (one-shot) or toggle (blue/white) - Auto mark seen on interact: locally marks messages as read when you send any message (text, photo, video, audio, sticker) --- README.md | 3 +- src/Features/StoriesAndMessages/SeenButtons.x | 110 +++++++++++++----- src/InstagramHeaders.h | 3 + src/Settings/TweakSettings.m | 21 ++++ src/Tweak.h | 3 +- src/Tweak.x | 4 +- 6 files changed, 115 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index a6cd8d5..b685300 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,8 @@ A feature-rich iOS tweak for Instagram, forked from [SCInsta](https://github.com ### Stories and messages - Keep deleted messages -- Manually mark messages as seen +- Manually mark messages as seen (button or toggle mode) **\*** +- Auto mark seen on send (marks messages as read when you send any message) **\*** - Disable typing status - Unlimited replay of direct stories - Disable view-once limitations diff --git a/src/Features/StoriesAndMessages/SeenButtons.x b/src/Features/StoriesAndMessages/SeenButtons.x index ef3fd52..50b4530 100644 --- a/src/Features/StoriesAndMessages/SeenButtons.x +++ b/src/Features/StoriesAndMessages/SeenButtons.x @@ -1,81 +1,126 @@ #import "../../InstagramHeaders.h" #import "../../Tweak.h" #import "../../Utils.h" +#import +#import +#import + // Seen buttons (in DMs) // - Enables no seen for messages // - Enables unlimited views of DM visual messages + +BOOL dmSeenToggleEnabled = NO; +static BOOL sciSeenAutoBypass = NO; + +static BOOL sciIsSeenToggleMode() { + return [[SCIUtils getStringPref:@"seen_mode"] isEqualToString:@"toggle"]; +} + +static BOOL sciAutoInteractEnabled() { + return [SCIUtils getBoolPref:@"remove_lastseen"] && [SCIUtils getBoolPref:@"seen_auto_on_interact"]; +} + +static void sciDoAutoSeen(IGDirectThreadViewController *threadVC) { + sciSeenAutoBypass = YES; + [threadVC markLastMessageAsSeen]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + sciSeenAutoBypass = NO; + }); +} + +// ============ AUTO SEEN ON SEND ============ + +static void (*orig_setHasSent)(id self, SEL _cmd, BOOL sent); +static void new_setHasSent(id self, SEL _cmd, BOOL sent) { + orig_setHasSent(self, _cmd, sent); + if (!sent || !sciAutoInteractEnabled()) return; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + sciDoAutoSeen((IGDirectThreadViewController *)self); + }); +} + +// ============ NAV BAR BUTTONS ============ + %hook IGTallNavigationBarView - (void)setRightBarButtonItems:(NSArray *)items { NSMutableArray *new_items = [[items filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(UIView *value, NSDictionary *_) { - if ([SCIUtils getBoolPref:@"hide_reels_blend"]) { + if ([SCIUtils getBoolPref:@"hide_reels_blend"]) return ![value.accessibilityIdentifier isEqualToString:@"blend-button"]; - } - return true; }] ] mutableCopy]; - // Messages seen if ([SCIUtils getBoolPref:@"remove_lastseen"]) { UIBarButtonItem *seenButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"checkmark.message"] style:UIBarButtonItemStylePlain target:self action:@selector(seenButtonHandler:)]; + if (sciIsSeenToggleMode()) + [seenButton setTintColor:dmSeenToggleEnabled ? SCIUtils.SCIColor_Primary : UIColor.labelColor]; [new_items addObject:seenButton]; } - // DM visual messages viewed if ([SCIUtils getBoolPref:@"unlimited_replay"]) { UIBarButtonItem *dmVisualMsgsViewedButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"photo.badge.checkmark"] style:UIBarButtonItemStylePlain target:self action:@selector(dmVisualMsgsViewedButtonHandler:)]; [new_items addObject:dmVisualMsgsViewedButton]; - - if (dmVisualMsgsViewedButtonEnabled) { - [dmVisualMsgsViewedButton setTintColor:SCIUtils.SCIColor_Primary]; - } else { - [dmVisualMsgsViewedButton setTintColor:UIColor.labelColor]; - } + [dmVisualMsgsViewedButton setTintColor:dmVisualMsgsViewedButtonEnabled ? SCIUtils.SCIColor_Primary : UIColor.labelColor]; } %orig([new_items copy]); } -// Messages seen button -%new - (void)seenButtonHandler:(UIBarButtonItem *)sender { - UIViewController *nearestVC = [SCIUtils nearestViewControllerForView:self]; - if ([nearestVC isKindOfClass:%c(IGDirectThreadViewController)]) { - [(IGDirectThreadViewController *)nearestVC markLastMessageAsSeen]; +// ============ MESSAGES SEEN BUTTON ============ - [SCIUtils showToastForDuration:2.5 title:@"Marked messages as seen"]; +%new - (void)seenButtonHandler:(UIBarButtonItem *)sender { + if (sciIsSeenToggleMode()) { + dmSeenToggleEnabled = !dmSeenToggleEnabled; + [sender setTintColor:dmSeenToggleEnabled ? SCIUtils.SCIColor_Primary : UIColor.labelColor]; + if (dmSeenToggleEnabled) { + UIViewController *nearestVC = [SCIUtils nearestViewControllerForView:self]; + if ([nearestVC isKindOfClass:%c(IGDirectThreadViewController)]) + [(IGDirectThreadViewController *)nearestVC markLastMessageAsSeen]; + [SCIUtils showToastForDuration:2.5 title:@"Read receipts enabled"]; + } else { + [SCIUtils showToastForDuration:2.5 title:@"Read receipts disabled"]; + } + } else { + UIViewController *nearestVC = [SCIUtils nearestViewControllerForView:self]; + if ([nearestVC isKindOfClass:%c(IGDirectThreadViewController)]) { + [(IGDirectThreadViewController *)nearestVC markLastMessageAsSeen]; + [SCIUtils showToastForDuration:2.5 title:@"Marked messages as seen"]; + } } } -// DM visual messages viewed button + +// ============ DM VISUAL MESSAGES VIEWED BUTTON ============ + %new - (void)dmVisualMsgsViewedButtonHandler:(UIBarButtonItem *)sender { if (dmVisualMsgsViewedButtonEnabled) { dmVisualMsgsViewedButtonEnabled = false; [sender setTintColor:UIColor.labelColor]; - [SCIUtils showToastForDuration:4.5 title:@"Visual messages can be replayed without expiring"]; - } - else { + } else { dmVisualMsgsViewedButtonEnabled = true; [sender setTintColor:SCIUtils.SCIColor_Primary]; - [SCIUtils showToastForDuration:4.5 title:@"Visual messages will now expire after viewing"]; } } %end -// Messages seen logic +// ============ SEEN BLOCKING LOGIC ============ + %hook IGDirectThreadViewListAdapterDataSource - (BOOL)shouldUpdateLastSeenMessage { if ([SCIUtils getBoolPref:@"remove_lastseen"]) { + if (sciIsSeenToggleMode() && dmSeenToggleEnabled) return %orig; + if (sciSeenAutoBypass) return %orig; return false; } - return %orig; } %end -// DM visual messages viewed logic +// ============ DM VISUAL MESSAGES VIEWED LOGIC ============ + %hook IGDirectVisualMessageViewerEventHandler - (void)visualMessageViewerController:(id)arg1 didBeginPlaybackForVisualMessage:(id)arg2 atIndex:(NSInteger)arg3 { if ([SCIUtils getBoolPref:@"unlimited_replay"] && !dmVisualMsgsViewedButtonEnabled) return; @@ -85,4 +130,17 @@ if ([SCIUtils getBoolPref:@"unlimited_replay"] && !dmVisualMsgsViewedButtonEnabled) return; %orig; } -%end \ No newline at end of file +%end + +// ============ RUNTIME HOOKS ============ + +%ctor { + Class threadVCClass = NSClassFromString(@"IGDirectThreadViewController"); + if (threadVCClass) { + SEL sentSel = NSSelectorFromString(@"setHasSentAMessageOrUpdate:"); + if (class_getInstanceMethod(threadVCClass, sentSel)) { + MSHookMessageEx(threadVCClass, sentSel, + (IMP)new_setHasSent, (IMP *)&orig_setHasSent); + } + } +} diff --git a/src/InstagramHeaders.h b/src/InstagramHeaders.h index fbcff91..365bfef 100644 --- a/src/InstagramHeaders.h +++ b/src/InstagramHeaders.h @@ -497,6 +497,9 @@ - (id)messageSenderFeatureController; @end +@interface IGDirectMessageSenderFeatureController : NSObject +@end + @interface IGTabBarButton : UIButton - (void)addHandleLongPress; // new @end diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index d4379ef..7fc98ca 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -141,6 +141,8 @@ @"rows": @[ [SCISetting switchCellWithTitle:@"Keep deleted messages" subtitle:@"Saves deleted messages in chat conversations" defaultsKey:@"keep_deleted_message"], [SCISetting switchCellWithTitle:@"Manually mark messages as seen" subtitle:@"Adds a button to DM threads, which will mark messages as seen" defaultsKey:@"remove_lastseen"], + [SCISetting menuCellWithTitle:@"Read receipt mode" subtitle:@"How the seen button behaves" menu:[self menus][@"seen_mode"]], + [SCISetting switchCellWithTitle:@"Auto: mark seen on interact" subtitle:@"Locally marks messages as seen when you send a message or react" defaultsKey:@"seen_auto_on_interact"], [SCISetting switchCellWithTitle:@"Disable typing status" subtitle:@"Prevents the typing indicator from being shown to others when you're typing in DMs" defaultsKey:@"disable_typing_status"], [SCISetting switchCellWithTitle:@"Send audio as file" subtitle:@"Adds an 'Audio File' option to the plus menu in DMs to send audio files as voice messages" defaultsKey:@"send_audio_as_file"], ] @@ -324,6 +326,25 @@ + (NSDictionary *)menus { return @{ + @"seen_mode": [UIMenu menuWithChildren:@[ + [UICommand commandWithTitle:@"Button" + image:nil + action:@selector(menuChanged:) + propertyList:@{ + @"defaultsKey": @"seen_mode", + @"value": @"button" + } + ], + [UICommand commandWithTitle:@"Toggle" + image:nil + action:@selector(menuChanged:) + propertyList:@{ + @"defaultsKey": @"seen_mode", + @"value": @"toggle" + } + ] + ]], + @"dw_save_action": [UIMenu menuWithChildren:@[ [UICommand commandWithTitle:@"Share sheet" image:nil diff --git a/src/Tweak.h b/src/Tweak.h index d4479f6..a0af3db 100644 --- a/src/Tweak.h +++ b/src/Tweak.h @@ -4,4 +4,5 @@ extern NSString *SCIVersionString; // Variables that work across features -extern BOOL dmVisualMsgsViewedButtonEnabled; // Whether story dm unlimited views button is enabled \ No newline at end of file +extern BOOL dmVisualMsgsViewedButtonEnabled; // Whether story dm unlimited views button is enabled +extern BOOL dmSeenToggleEnabled; // Whether read receipts toggle is active \ No newline at end of file diff --git a/src/Tweak.x b/src/Tweak.x index 7406ab0..f9f23e0 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -47,7 +47,9 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; @"doom_scrolling_reel_count": @(1), @"no_seen_visual": @(YES), @"send_audio_as_file": @(YES), - @"unlock_password_reels": @(YES) + @"unlock_password_reels": @(YES), + @"seen_mode": @"button", + @"seen_auto_on_interact": @(YES) }; [[NSUserDefaults standardUserDefaults] registerDefaults:sciDefaults];