feat: Replace Domain in Instagram urls

feat: Remove user param from copied links
feat: Open links in External Browser
feat: Strip Instagram tracking from browser links
fix: Confirm reel refresh confirmation appears on app launch
#2
This commit is contained in:
faroukbmiled
2026-04-10 09:43:41 +01:00
parent de3e13f60a
commit 3693f3e93a
9 changed files with 388 additions and 18 deletions
+99
View File
@@ -0,0 +1,99 @@
// Rewrite Instagram share links — replace domain + optionally strip tracking params.
// Waits for IG's async clipboard write via changeCount, then rewrites once.
#import "../../Utils.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import <substrate.h>
static NSString *sciRewriteIGURL(NSString *url) {
if (!url.length) return url;
// Domain replacement
if ([SCIUtils getBoolPref:@"embed_links"]) {
NSString *domain = [SCIUtils getStringPref:@"embed_link_domain"];
if (!domain.length) domain = @"kkinstagram.com";
if (![url containsString:domain]) {
NSArray *igDomains = @[@"www.instagram.com", @"instagram.com", @"www.instagr.am", @"instagr.am"];
for (NSString *d in igDomains) {
NSRange r = [url rangeOfString:d];
if (r.location != NSNotFound) {
NSString *target = [d hasPrefix:@"www."]
? [NSString stringWithFormat:@"www.%@", domain] : domain;
url = [url stringByReplacingCharactersInRange:r withString:target];
break;
}
}
}
}
// Strip tracking params
if ([SCIUtils getBoolPref:@"strip_tracking_params"]) {
NSURLComponents *comps = [NSURLComponents componentsWithString:url];
if (comps.queryItems.count) {
NSArray *strip = @[@"igsh", @"ig_rid", @"utm_source", @"utm_medium", @"utm_campaign"];
NSMutableArray *clean = [NSMutableArray array];
for (NSURLQueryItem *q in comps.queryItems) {
if (![strip containsObject:q.name]) [clean addObject:q];
}
comps.queryItems = clean.count ? clean : nil;
NSString *result = comps.string;
if (result) url = result;
}
}
return url;
}
static BOOL sciShouldRewrite(void) {
return [SCIUtils getBoolPref:@"embed_links"] || [SCIUtils getBoolPref:@"strip_tracking_params"];
}
// Rewrite clipboard once after IG writes
static void sciPollAndRewrite(NSInteger countBefore, int polls, double interval) {
__block BOOL done = NO;
for (int i = 0; i < polls; i++) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((interval + i * interval) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (done) return;
if ([UIPasteboard generalPasteboard].changeCount == countBefore) return;
NSString *clip = [UIPasteboard generalPasteboard].string;
if (!clip || ![clip containsString:@"instagram"]) return;
NSString *rewritten = sciRewriteIGURL(clip);
if (![rewritten isEqualToString:clip]) {
[UIPasteboard generalPasteboard].string = rewritten;
done = YES;
} else {
done = YES;
}
});
}
}
// ============ Hooks ============
static void (*orig_copyLink)(id, SEL, id);
static void new_copyLink(id self, SEL _cmd, id vc) {
if (!sciShouldRewrite()) { orig_copyLink(self, _cmd, vc); return; }
NSInteger countBefore = [UIPasteboard generalPasteboard].changeCount;
orig_copyLink(self, _cmd, vc);
sciPollAndRewrite(countBefore, 30, 0.05);
}
static void (*orig_shareMore)(id, SEL, id);
static void new_shareMore(id self, SEL _cmd, id vc) {
if (!sciShouldRewrite()) { orig_shareMore(self, _cmd, vc); return; }
NSInteger countBefore = [UIPasteboard generalPasteboard].changeCount;
orig_shareMore(self, _cmd, vc);
sciPollAndRewrite(countBefore, 120, 0.1);
}
__attribute__((constructor)) static void _embedLinksInit(void) {
Class cls = NSClassFromString(@"IGExternalShareOptionsViewController");
if (!cls) return;
SEL copy = NSSelectorFromString(@"_shareToClipboardFromVC:");
if (class_getInstanceMethod(cls, copy))
MSHookMessageEx(cls, copy, (IMP)new_copyLink, (IMP *)&orig_copyLink);
SEL more = NSSelectorFromString(@"_shareToMoreFromVC:");
if (class_getInstanceMethod(cls, more))
MSHookMessageEx(cls, more, (IMP)new_shareMore, (IMP *)&orig_shareMore);
}
+69
View File
@@ -0,0 +1,69 @@
// Open links in external browser + strip IG tracking from URLs
#import "../../Utils.h"
#import <objc/runtime.h>
#import <objc/message.h>
// Extract the real URL from l.instagram.com redirects and strip tracking params
static NSURL *sciCleanBrowserURL(NSURL *url) {
if (![SCIUtils getBoolPref:@"strip_browser_tracking"]) return url;
if (!url) return url;
NSString *urlStr = url.absoluteString;
// Unwrap l.instagram.com/?u=ENCODED_URL&e=TRACKING redirects
if ([url.host isEqualToString:@"l.instagram.com"]) {
NSURLComponents *comps = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
for (NSURLQueryItem *q in comps.queryItems) {
if ([q.name isEqualToString:@"u"] && q.value.length) {
NSString *decoded = [q.value stringByRemovingPercentEncoding];
if (decoded) urlStr = decoded;
break;
}
}
}
// Strip common tracking params from the destination URL
NSURLComponents *comps = [NSURLComponents componentsWithString:urlStr];
if (comps.queryItems.count) {
NSSet *trackingParams = [NSSet setWithArray:@[
@"utm_source", @"utm_medium", @"utm_campaign", @"utm_content",
@"utm_term", @"utm_id", @"fbclid", @"igshid", @"igsh",
@"ig_rid", @"campaign_id", @"ad_id", @"aem"
]];
NSMutableArray *clean = [NSMutableArray array];
for (NSURLQueryItem *q in comps.queryItems) {
if (![trackingParams containsObject:q.name]) [clean addObject:q];
}
comps.queryItems = clean.count ? clean : nil;
}
NSURL *result = comps.URL;
return result ?: url;
}
%hook IGBrowserNavigationController
- (void)viewWillAppear:(BOOL)animated {
id session = ((id(*)(id,SEL))objc_msgSend)(self, @selector(browserSession));
Ivar urlIvar = session ? class_getInstanceVariable([session class], "_urlRequest") : nil;
NSURLRequest *req = urlIvar ? object_getIvar(session, urlIvar) : nil;
NSURL *url = req.URL;
if (url && [SCIUtils getBoolPref:@"open_links_external"]) {
NSURL *cleaned = sciCleanBrowserURL(url);
[[UIApplication sharedApplication] openURL:cleaned options:@{} completionHandler:nil];
[(UIViewController *)self dismissViewControllerAnimated:NO completion:nil];
return;
}
// For in-app browser: replace the URL request with the cleaned version
if (url && [SCIUtils getBoolPref:@"strip_browser_tracking"]) {
NSURL *cleaned = sciCleanBrowserURL(url);
if (![cleaned isEqual:url]) {
NSURLRequest *cleanReq = [NSURLRequest requestWithURL:cleaned];
object_setIvar(session, urlIvar, cleanReq);
}
}
%orig;
}
%end
+33 -14
View File
@@ -28,27 +28,46 @@
}
%end
static BOOL sciReelRefreshInFlight = NO;
%hook IGSundialFeedViewController
- (void)_refreshReelsWithParamsForNetworkRequest:(NSInteger)arg1 userDidPullToRefresh:(BOOL)arg2 {
if ([SCIUtils getBoolPref:@"prevent_doom_scrolling"]) {
IGRefreshControl *_refreshControl = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
[self refreshControlDidEndFinishLoadingAnimation:_refreshControl];
IGRefreshControl *rc = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
[self refreshControlDidEndFinishLoadingAnimation:rc];
return;
}
if ([SCIUtils getBoolPref:@"refresh_reel_confirm"]) {
NSLog(@"[SCInsta] Reel refresh triggered");
[SCIUtils showConfirmation:^(void) { %orig(arg1, arg2); }
cancelHandler:^(void) {
IGRefreshControl *_refreshControl = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
[self refreshControlDidEndFinishLoadingAnimation:_refreshControl];
}
title:@"Refresh Reels"];
} else {
return %orig(arg1, arg2);
if (![(UIViewController *)self isViewLoaded] || sciReelRefreshInFlight || ![SCIUtils getBoolPref:@"refresh_reel_confirm"]) {
%orig(arg1, arg2);
return;
}
// Reset the refresh control state so pull-to-refresh can trigger again
IGRefreshControl *rc = MSHookIvar<IGRefreshControl *>(self, "_refreshControl");
Ivar stateIvar = class_getInstanceVariable([rc class], "_refreshState");
if (stateIvar) {
ptrdiff_t off = ivar_getOffset(stateIvar);
*(NSInteger *)((char *)(__bridge void *)rc + off) = 0;
}
if ([rc respondsToSelector:@selector(endRefreshing)])
((void(*)(id,SEL))objc_msgSend)(rc, @selector(endRefreshing));
[self refreshControlDidEndFinishLoadingAnimation:rc];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Refresh Reels?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak id weakSelf = self;
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:@"Refresh" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) {
sciReelRefreshInFlight = YES;
SEL rSel = @selector(_refreshReelsWithParamsForNetworkRequest:userDidPullToRefresh:);
((void(*)(id,SEL,NSInteger,BOOL))objc_msgSend)(weakSelf, rSel, arg1, arg2);
sciReelRefreshInFlight = NO;
}]];
UIViewController *presenter = (UIViewController *)self;
[presenter presentViewController:alert animated:YES completion:nil];
}
%end