From 7952877545bf61a549ecef3a78dbef19c2443a53 Mon Sep 17 00:00:00 2001 From: faroukbmiled Date: Fri, 10 Apr 2026 11:12:14 +0100 Subject: [PATCH] feat: Auto-advance on story like --- README.md | 1 + .../StoriesAndMessages/DisableStorySeen.x | 61 +++++++++++++++---- src/Settings/TweakSettings.m | 1 + src/Tweak.x | 1 + 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 871a9a0..558bb32 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ A feature-rich iOS tweak for Instagram, forked from [SCInsta](https://github.com - Auto mark seen on typing (marks messages as read the moment you start typing, even when typing status is hidden) **\*** - Mark seen on story like **\*** - Advance to next story when marking as seen — tapping the eye button auto-skips to the next story **\*** +- Advance on story like — liking a story auto-skips to the next one **\*** - Per-chat read-receipt exclusions — long-press any DM chat to exclude it from all read-receipt features. Excluded chats behave like a vanilla install (manual seen button, auto seen on send/typing, visual message blocking, view-once override are all skipped). Settings page lists excluded chats with search, sort, swipe-to-remove, and a per-entry override to also bypass keep-deleted-messages **\*** - Send audio as file — send audio files as voice messages from the DM plus menu **\*** - Download voice messages — adds a Download option to the long-press menu on voice messages, saves as M4A via share sheet **\*** diff --git a/src/Features/StoriesAndMessages/DisableStorySeen.x b/src/Features/StoriesAndMessages/DisableStorySeen.x index 7b5f17e..38cf960 100644 --- a/src/Features/StoriesAndMessages/DisableStorySeen.x +++ b/src/Features/StoriesAndMessages/DisableStorySeen.x @@ -119,12 +119,9 @@ static BOOL sciShouldBlockSeenVisualForObj(id obj) { - (void)updateRingForSeenState:(BOOL)arg1 { if (sciShouldBlockSeenVisual()) { %orig(NO); return; } %orig; } %end -// ============ MARK SEEN ON STORY LIKE ============ -// Story likes are dispatched through several classes — IGSundialViewerControlsOverlayController, -// IGStoryLikesInteractionControllingImpl, and IGSundialLikeButton. Hook every -// known entry so any UI path (heart button, future double-tap, etc.) is caught. -// On a like, we defer to the manual seen-button handler in OverlayButtons.xm -// (sciMarkSeenTapped:) which is the only flow IG actually treats as a real seen. +// ============ STORY LIKE HOOKS ============ +// Hooks all known like entry points to trigger mark-seen and auto-advance on like. +// Uses sciMarkSeenTapped: from OverlayButtons.xm for the actual seen flow. static __weak UIViewController *sciActiveStoryVC = nil; @@ -158,33 +155,72 @@ static void sciMarkActiveStorySeen(void) { UIView *overlay = sciFindStoryOverlayView(sciActiveStoryVC); if (!overlay) return; SEL sel = NSSelectorFromString(@"sciMarkSeenTapped:"); - if ([overlay respondsToSelector:sel]) { + if ([overlay respondsToSelector:sel]) ((void(*)(id, SEL, id))objc_msgSend)(overlay, sel, nil); - } +} + +// Dedup guard — multiple hooks fire for the same like event +static uint64_t sciLastLikeAdvanceTime = 0; + +static void sciAdvanceOnStoryLike(void) { + if (![SCIUtils getBoolPref:@"advance_on_story_like"]) return; + UIViewController *storyVC = sciActiveStoryVC; + if (!storyVC) return; + id sectionCtrl = sciFindSectionController(storyVC); + if (!sectionCtrl) return; + + uint64_t now = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); + if (now - sciLastLikeAdvanceTime < 500000000ULL) return; + sciLastLikeAdvanceTime = now; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + sciAdvanceBypassActive = YES; + SEL advSel = NSSelectorFromString(@"advanceToNextItemWithNavigationAction:"); + if ([sectionCtrl respondsToSelector:advSel]) + ((void(*)(id, SEL, NSInteger))objc_msgSend)(sectionCtrl, advSel, 1); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + id sc2 = storyVC ? sciFindSectionController(storyVC) : nil; + if (sc2) { + SEL resumeSel = NSSelectorFromString(@"tryResumePlaybackWithReason:"); + if ([sc2 respondsToSelector:resumeSel]) + ((void(*)(id, SEL, NSInteger))objc_msgSend)(sc2, resumeSel, 0); + } + sciAdvanceBypassActive = NO; + }); + }); +} + +static void sciOnStoryLike(void) { + sciMarkActiveStorySeen(); + sciAdvanceOnStoryLike(); } static void (*orig_didLikeSundial)(id, SEL, id); static void new_didLikeSundial(id self, SEL _cmd, id pk) { orig_didLikeSundial(self, _cmd, pk); - sciMarkActiveStorySeen(); + sciOnStoryLike(); } static void (*orig_overlaySetIsLiked)(id, SEL, BOOL, BOOL); static void new_overlaySetIsLiked(id self, SEL _cmd, BOOL isLiked, BOOL animated) { orig_overlaySetIsLiked(self, _cmd, isLiked, animated); - if (isLiked) sciMarkActiveStorySeen(); + if (isLiked) sciOnStoryLike(); } +// IGUFIButton selected state: YES = heart filled (liked), NO = empty (not liked). +// handleStoryLikeTapWithButton: is a toggle — check state before orig to determine direction. static void (*orig_handleLikeTap)(id, SEL, id); static void new_handleLikeTap(id self, SEL _cmd, id button) { + BOOL isLike = [button isKindOfClass:[UIButton class]] && [(UIButton *)button isSelected]; orig_handleLikeTap(self, _cmd, button); - sciMarkActiveStorySeen(); + if (isLike) sciOnStoryLike(); } static void (*orig_likeButtonSetIsLiked)(id, SEL, BOOL, BOOL); static void new_likeButtonSetIsLiked(id self, SEL _cmd, BOOL isLiked, BOOL animated) { orig_likeButtonSetIsLiked(self, _cmd, isLiked, animated); - if (isLiked) sciMarkActiveStorySeen(); + if (isLiked) sciOnStoryLike(); } %ctor { @@ -212,5 +248,4 @@ static void new_likeButtonSetIsLiked(id self, SEL _cmd, BOOL isLiked, BOOL anima if (class_getInstanceMethod(likeBtn, setLiked)) MSHookMessageEx(likeBtn, setLiked, (IMP)new_likeButtonSetIsLiked, (IMP *)&orig_likeButtonSetIsLiked); } - } diff --git a/src/Settings/TweakSettings.m b/src/Settings/TweakSettings.m index cc840a2..c71d692 100644 --- a/src/Settings/TweakSettings.m +++ b/src/Settings/TweakSettings.m @@ -205,6 +205,7 @@ @"rows": @[ [SCISetting switchCellWithTitle:@"Stop story auto-advance" subtitle:@"Stories won't auto-skip to the next one when the timer ends. Tap to advance manually" defaultsKey:@"stop_story_auto_advance"], [SCISetting switchCellWithTitle:@"Advance when marking as seen" subtitle:@"Tapping the eye button to mark a story as seen advances to the next story automatically" defaultsKey:@"advance_on_mark_seen"], + [SCISetting switchCellWithTitle:@"Advance on story like" subtitle:@"Liking a story automatically advances to the next one after a short delay" defaultsKey:@"advance_on_story_like"], ] }, @{ diff --git a/src/Tweak.x b/src/Tweak.x index f2872d1..218e253 100644 --- a/src/Tweak.x +++ b/src/Tweak.x @@ -57,6 +57,7 @@ BOOL dmVisualMsgsViewedButtonEnabled = false; @"seen_auto_on_typing": @(NO), @"seen_on_story_like": @(NO), @"advance_on_mark_seen": @(NO), + @"advance_on_story_like": @(NO), @"indicate_unsent_messages": @(NO), @"unsent_message_toast": @(NO), @"warn_refresh_clears_preserved": @(NO),