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
+4
View File
@@ -25,6 +25,10 @@ A feature-rich iOS tweak for Instagram, forked from [SCInsta](https://github.com
- Copy comment text from long-press menu **\***
- Download GIF comments **\***
- Profile copy button **\***
- Replace domain in shared links — rewrite copied/shared links for embeds in Discord, Telegram, etc. with preset or custom domains **\***
- Strip tracking params from shared links (igsh, utm) **\***
- Open links in external browser **\***
- Strip tracking from browser links **\***
- Do not save recent searches
- Use detailed (native) color picker
- Enable liquid glass buttons
+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
@@ -0,0 +1,4 @@
#import <UIKit/UIKit.h>
@interface SCIEmbedDomainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
@end
+130
View File
@@ -0,0 +1,130 @@
#import "SCIEmbedDomainViewController.h"
#import "../Utils.h"
#define SCI_CUSTOM_DOMAINS_KEY @"embed_custom_domains"
static NSArray *sciPresetDomains(void) {
return @[@"kkinstagram.com", @"ddinstagram.com", @"d.ddinstagram.com", @"g.ddinstagram.com"];
}
@interface SCIEmbedDomainViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, copy) NSArray<NSString *> *customDomains;
@end
@implementation SCIEmbedDomainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Embed domain";
self.view.backgroundColor = [UIColor systemBackgroundColor];
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.tableView];
[NSLayoutConstraint activateConstraints:@[
[self.tableView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
]];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addCustom)];
[self reload];
}
- (void)reload {
NSArray *stored = [[NSUserDefaults standardUserDefaults] arrayForKey:SCI_CUSTOM_DOMAINS_KEY];
self.customDomains = stored ?: @[];
[self.tableView reloadData];
}
- (void)addCustom {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Add custom domain"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *tf) {
tf.placeholder = @"example.com";
tf.autocapitalizationType = UITextAutocapitalizationTypeNone;
tf.autocorrectionType = UITextAutocorrectionTypeNo;
tf.keyboardType = UIKeyboardTypeURL;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Add" style:UIAlertActionStyleDefault handler:^(UIAlertAction *_) {
NSString *domain = alert.textFields.firstObject.text;
domain = [domain stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
domain = [domain stringByReplacingOccurrencesOfString:@"https://" withString:@""];
domain = [domain stringByReplacingOccurrencesOfString:@"http://" withString:@""];
while ([domain hasSuffix:@"/"]) domain = [domain substringToIndex:domain.length - 1];
if (!domain.length || ![domain containsString:@"."]) return;
NSMutableArray *all = [self.customDomains mutableCopy];
if (![all containsObject:domain]) [all addObject:domain];
[[NSUserDefaults standardUserDefaults] setObject:all forKey:SCI_CUSTOM_DOMAINS_KEY];
[[NSUserDefaults standardUserDefaults] setObject:domain forKey:@"embed_link_domain"];
[self reload];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv { return 2; }
- (NSString *)tableView:(UITableView *)tv titleForHeaderInSection:(NSInteger)section {
return section == 0 ? @"Presets" : @"Custom";
}
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section {
return section == 0 ? (NSInteger)sciPresetDomains().count : (NSInteger)self.customDomains.count;
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
NSString *domain = indexPath.section == 0
? sciPresetDomains()[indexPath.row]
: self.customDomains[indexPath.row];
cell.textLabel.text = domain;
NSString *current = [SCIUtils getStringPref:@"embed_link_domain"];
if (!current.length) current = @"kkinstagram.com";
cell.accessoryType = [domain isEqualToString:current] ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
return cell;
}
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tv deselectRowAtIndexPath:indexPath animated:YES];
NSString *domain = indexPath.section == 0
? sciPresetDomains()[indexPath.row]
: self.customDomains[indexPath.row];
[[NSUserDefaults standardUserDefaults] setObject:domain forKey:@"embed_link_domain"];
[tv reloadData];
}
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tv trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) return nil;
NSString *domain = self.customDomains[indexPath.row];
UIContextualAction *del = [UIContextualAction
contextualActionWithStyle:UIContextualActionStyleDestructive title:@"Delete"
handler:^(UIContextualAction *_, UIView *__, void (^cb)(BOOL)) {
NSMutableArray *all = [self.customDomains mutableCopy];
[all removeObject:domain];
[[NSUserDefaults standardUserDefaults] setObject:all forKey:SCI_CUSTOM_DOMAINS_KEY];
// Reset to default if deleted domain was selected
NSString *current = [SCIUtils getStringPref:@"embed_link_domain"];
if ([current isEqualToString:domain])
[[NSUserDefaults standardUserDefaults] setObject:@"kkinstagram.com" forKey:@"embed_link_domain"];
[self reload];
cb(YES);
}];
return [UISwipeActionsConfiguration configurationWithActions:@[del]];
}
- (BOOL)tableView:(UITableView *)tv canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return indexPath.section == 1;
}
@end
+1
View File
@@ -405,6 +405,7 @@ typedef NS_ENUM(NSInteger, SCIBackupPreviewRowKind) {
return @[
@"excluded_threads",
@"excluded_story_users",
@"embed_custom_domains",
];
}
+43 -4
View File
@@ -4,6 +4,7 @@
#import "../Features/StoriesAndMessages/SCIExcludedThreads.h"
#import "../Features/StoriesAndMessages/SCIExcludedStoryUsers.h"
#import "SCIExcludedStoryUsersViewController.h"
#import "SCIEmbedDomainViewController.h"
@implementation SCITweakSettings
@@ -37,13 +38,42 @@
@"rows": @[
[SCISetting switchCellWithTitle:@"Hide ads" subtitle:@"Removes all ads from the Instagram app" defaultsKey:@"hide_ads"],
[SCISetting switchCellWithTitle:@"Hide Meta AI" subtitle:@"Hides the meta ai buttons/functionality within the app" defaultsKey:@"hide_meta_ai"],
[SCISetting switchCellWithTitle:@"Do not save recent searches" subtitle:@"Search bars will no longer save your recent searches" defaultsKey:@"no_recent_searches"],
[SCISetting switchCellWithTitle:@"Copy description" subtitle:@"Copy description text fields by long-pressing on them" defaultsKey:@"copy_description"],
[SCISetting switchCellWithTitle:@"Profile copy button" subtitle:@"Adds a button next to the burger menu on profiles to copy username, name or bio" defaultsKey:@"profile_copy_button"],
[SCISetting switchCellWithTitle:@"Do not save recent searches" subtitle:@"Search bars will no longer save your recent searches" defaultsKey:@"no_recent_searches"],
[SCISetting switchCellWithTitle:@"Use detailed color picker" subtitle:@"Long press on the eyedropper tool in stories to customize the text color more precisely" defaultsKey:@"detailed_color_picker"],
[SCISetting switchCellWithTitle:@"Enable liquid glass buttons" subtitle:@"Enables experimental liquid glass buttons within the app" defaultsKey:@"liquid_glass_buttons" requiresRestart:YES],
[SCISetting switchCellWithTitle:@"Enable liquid glass surfaces" subtitle:@"Enables liquid glass for other elements, such as menus" defaultsKey:@"liquid_glass_surfaces" requiresRestart:YES],
[SCISetting switchCellWithTitle:@"Enable teen app icons" subtitle:@"When enabled, hold down on the Instagram logo to change the app icon" defaultsKey:@"teen_app_icons" requiresRestart:YES]
]
},
@{
@"header": @"Browser",
@"rows": @[
[SCISetting switchCellWithTitle:@"Open links in external browser" subtitle:@"Opens links in Safari instead of Instagram's in-app browser" defaultsKey:@"open_links_external"],
[SCISetting switchCellWithTitle:@"Strip tracking from links" subtitle:@"Removes Instagram tracking wrappers (l.instagram.com) and UTM/fbclid params from URLs" defaultsKey:@"strip_browser_tracking"],
]
},
@{
@"header": @"Sharing",
@"rows": @[
[SCISetting switchCellWithTitle:@"Replace domain in shared links" subtitle:@"Rewrites copied/shared links to use an embed-friendly domain for previews in Discord, Telegram, etc." defaultsKey:@"embed_links"],
({
SCISetting *s = [SCISetting buttonCellWithTitle:@"Embed domain"
subtitle:@""
icon:[SCISymbol symbolWithName:@"globe"]
action:^(void) {
UIWindow *win = nil;
for (UIWindow *w in [UIApplication sharedApplication].windows)
if (w.isKeyWindow) { win = w; break; }
UIViewController *top = win.rootViewController;
while (top.presentedViewController) top = top.presentedViewController;
if ([top isKindOfClass:[UINavigationController class]])
[(UINavigationController *)top pushViewController:[SCIEmbedDomainViewController new] animated:YES];
else if (top.navigationController)
[top.navigationController pushViewController:[SCIEmbedDomainViewController new] animated:YES];
}];
s.dynamicTitle = ^{ return [NSString stringWithFormat:@"Embed domain: %@", [SCIUtils getStringPref:@"embed_link_domain"] ?: @"kkinstagram.com"]; };
s;
}),
[SCISetting switchCellWithTitle:@"Strip tracking params" subtitle:@"Removes igsh, utm_source, and other tracking parameters from shared links" defaultsKey:@"strip_tracking_params"],
]
},
@{
@@ -70,6 +100,15 @@
[SCISetting switchCellWithTitle:@"Hide explore posts grid" subtitle:@"Hides the grid of suggested posts on the explore/search tab" defaultsKey:@"hide_explore_grid"],
[SCISetting switchCellWithTitle:@"Hide trending searches" subtitle:@"Hides the trending searches under the explore search bar" defaultsKey:@"hide_trending_searches"],
]
},
@{
@"header": @"Experimental features",
@"footer": @"These features rely on hidden Instagram flags and may not work on all accounts or versions.",
@"rows": @[
[SCISetting switchCellWithTitle:@"Enable liquid glass buttons" subtitle:@"Enables experimental liquid glass buttons" defaultsKey:@"liquid_glass_buttons" requiresRestart:YES],
[SCISetting switchCellWithTitle:@"Enable liquid glass surfaces" subtitle:@"Enables liquid glass for other elements" defaultsKey:@"liquid_glass_surfaces" requiresRestart:YES],
[SCISetting switchCellWithTitle:@"Enable teen app icons" subtitle:@"Hold down on the Instagram logo to change the app icon" defaultsKey:@"teen_app_icons" requiresRestart:YES]
]
}]
],
[SCISetting navigationCellWithTitle:@"Feed"
+5
View File
@@ -68,6 +68,11 @@ BOOL dmVisualMsgsViewedButtonEnabled = false;
@"story_seen_mode": @"button",
@"story_audio_toggle": @(NO),
@"settings_pause_playback": @(YES),
@"embed_links": @(NO),
@"embed_link_domain": @"kkinstagram.com",
@"strip_tracking_params": @(NO),
@"open_links_external": @(NO),
@"strip_browser_tracking": @(NO),
@"hide_feed_repost": @(NO),
@"copy_comment": @(YES),
@"download_gif_comment": @(YES)