Skip to content

Commit

Permalink
FLEX integration and working New Comments Highlighter (#32)
Browse files Browse the repository at this point in the history
* Add FLEX debugging support

* Update README

* Update Action to support submodule

* Hybrid implementation of New Comments Highlightifier

* Reduce image size

* Update changelog
  • Loading branch information
JeffreyCA authored Jul 27, 2024
1 parent 534cd40 commit df07612
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/buildapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
steps:
- name: Checkout main
uses: actions/checkout@v4
with:
submodules: 'recursive'

- name: Parse control file version
id: parse_control
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "Tweaks/FLEXing"]
path = Tweaks/FLEXing
url = https://github.com/PoomSmart/FLEXing
branch = rootless
6 changes: 3 additions & 3 deletions B64ImageEncodings.h

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to this project will be documented in this file.

## [v1.1.1] - 2024-07-27

- Working hybrid implementation of "New Comments Highlighter" Ultra feature
- Add FLEX integration for debugging/tweaking purposes (requires app restart after enabling in Settings -> General -> Custom API)

## [v1.0.12] - 2024-07-25

Use generic user agent independent of bundle ID when sending requests to Reddit
Expand Down Expand Up @@ -58,6 +63,7 @@ There are currently a few limitations:
## [v1.0.0] - 2023-10-13
- Initial release

[v1.0.12]: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/compare/v1.0.12...v1.1.1
[v1.0.12]: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/compare/v1.0.11...v1.0.12
[v1.0.11]: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/compare/v1.0.10...v1.0.11
[v1.0.10]: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/compare/v1.0.9...v1.0.10
Expand Down
12 changes: 6 additions & 6 deletions CustomAPIViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ - (void)viewDidLoad {
UIStackView *unreadCommentsStackView = [self createToggleSwitchWithKey:UDKeyApolloShowUnreadComments labelText:@"New Comments Highlightifier (Beta)" action:@selector(unreadCommentsSwitchToggled:)];
[stackView addArrangedSubview:unreadCommentsStackView];

UIStackView *weatherStackView = [self createToggleSwitchWithKey:UDKeyApolloSubredditWeather labelText:@"Subreddit Weather and Time (Beta)" action:@selector(weatherSwitchToggled:)];
[stackView addArrangedSubview:weatherStackView];
UIStackView *flexStackView = [self createToggleSwitchWithKey:UDKeyEnableFLEX labelText:@"FLEX Debugging (Needs restart)" action:@selector(flexSwitchToggled:)];
[stackView addArrangedSubview:flexStackView];

UITextView *textView = [[UITextView alloc] init];
textView.editable = NO;
Expand Down Expand Up @@ -218,13 +218,13 @@ - (void)unreadCommentsSwitchToggled:(UISwitch *)sender {
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:UDKeyApolloShowUnreadComments];
}

- (void)weatherSwitchToggled:(UISwitch *)sender {
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:UDKeyApolloSubredditWeather];
}

- (void)blockAnnouncementsSwitchToggled:(UISwitch *)sender {
sBlockAnnouncements = sender.isOn;
[[NSUserDefaults standardUserDefaults] setBool:sBlockAnnouncements forKey:UDKeyBlockAnnouncements];
}

- (void)flexSwitchToggled:(UISwitch *)sender {
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:UDKeyEnableFLEX];
}

@end
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
ARCHS = arm64
export ARCHS = arm64
export libFLEX_ARCHS = arm64

TARGET := iphone:clang:latest:14.0
INSTALL_TARGET_PROCESSES = Apollo
THEOS_LEAN_AND_MEAN = 1
# THEOS_PACKAGE_SCHEME=rootless

include $(THEOS)/makefiles/common.mk

Expand All @@ -12,4 +14,6 @@ ApolloImprovedCustomApi_FILES = Tweak.xm CustomAPIViewController.m UIWindow+Apol
ApolloImprovedCustomApi_FRAMEWORKS = UIKit
ApolloImprovedCustomApi_CFLAGS = -fobjc-arc -Wno-unguarded-availability-new -Wno-module-import-in-extern-c

SUBPROJECTS += Tweaks/FLEXing/libflex
include $(THEOS_MAKE_PATH)/aggregate.mk
include $(THEOS_MAKE_PATH)/tweak.mk
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ Apollo for Reddit with in-app configurable API keys and several fixes and improv
- Suppress unwanted messages on app startup (wallpaper popup, in-app announcements, etc)
- Support new share link format (reddit.com/r/subreddit/s/xxxxxx) so they open like any other post and not in a browser
- Support media share links (reddit.com/media?url=)
- Partially working "New Comments Highlightifier" Ultra feature (new comment count only)
- **Fully working** "New Comments Highlightifier" Ultra feature
- Randomize "trending subreddits list" so it doesn't show **iOS**, **Clock**, **Time**, **IfYouDontMind** all the time
- Use generic user agent for requests to Reddit
- Support FLEX debugging

## Known issues
- Apollo Ultra features may cause app to crash
Expand All @@ -37,9 +39,11 @@ Recommended configuration:
- [Theos](https://github.com/theos/theos)

1. `git clone`
2. `git submodule update --init --recursive`
2. `make package`

## Credits
- [Apollo-CustomApiCredentials](https://github.com/EthanArbuckle/Apollo-CustomApiCredentials) by [@EthanArbuckle](https://github.com/EthanArbuckle)
- [ApolloAPI](https://github.com/ryannair05/ApolloAPI) by [@ryannair05](https://github.com/ryannair05)
- [ApolloPatcher](https://github.com/ichitaso/ApolloPatcher) by [@ichitaso](https://github.com/ichitaso)
- [GitHub Copilot](https://github.com/features/copilot)
10 changes: 10 additions & 0 deletions Tweak.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#import <Foundation/Foundation.h>

#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

@interface ShareUrlTask : NSObject

@property (nonatomic) dispatch_group_t dispatchGroup;
Expand All @@ -11,4 +13,12 @@
@property(copy, nonatomic) NSURL *URL;
@end

@interface RDKComment
{
NSDate *_createdUTC;
NSString *_linkID;
}
- (id)linkIDWithoutTypePrefix;
@end

@class _TtC6Apollo14LinkButtonNode;
114 changes: 109 additions & 5 deletions Tweak.xm
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ static OSStatus SecItemUpdate_replacement(CFDictionaryRef query, CFDictionaryRef
return ((OSStatus (*)(CFDictionaryRef, CFDictionaryRef))SecItemUpdate_orig)((__bridge CFDictionaryRef)strippedQuery, attributesToUpdate);
}

static NSString *announcementUrl = @"https://apollogur.download/api/apollonouncement";
static NSString *const announcementUrl = @"https://apollogur.download/api/apollonouncement";

static NSArray *blockedUrls = @[
static NSArray *const blockedUrls = @[
@"https://apollopushserver.xyz",
@"telemetrydeck.com",
@"https://apollogur.download/api/easter_sale",
Expand All @@ -43,7 +43,10 @@ static NSArray *blockedUrls = @[
@"https://apollogur.download/api/goodbye_wallpaper"
];

static NSString *defaultUserAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";
static NSString *const defaultUserAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";

// Highlight color for new unread comments
static UIColor *const NewPostCommentsColor = [UIColorFromRGB(0xFFD16E) colorWithAlphaComponent: 0.15];

// Regex for opaque share links
static NSString *const ShareLinkRegexPattern = @"^(?:https?:)?//(?:www\\.)?reddit\\.com/(?:r|u)/(\\w+)/s/(\\w+)$";
Expand All @@ -56,6 +59,9 @@ static NSRegularExpression *MediaShareLinkRegex;
// Cache storing resolved share URLs - this is an optimization so that we don't need to resolve the share URL every time
static NSCache <NSString *, ShareUrlTask *> *cache;

// Dictionary of post IDs to last-read timestamp for tracking new unread comments
static NSMutableDictionary<NSString *, NSDate *> *postSnapshots;

@implementation ShareUrlTask
- (instancetype)init {
self = [super init];
Expand Down Expand Up @@ -274,9 +280,38 @@ static void TryResolveShareUrl(NSString *urlString, void (^successHandler)(NSStr

%end

@interface _TtC6Apollo15CommentCellNode
- (void)didLoad;
- (void)linkButtonTappedWithSender:(_TtC6Apollo14LinkButtonNode *)arg1;
@end

// Single comment under an individual post
%hook _TtC6Apollo15CommentCellNode

- (void)didLoad {
%orig;
if ([[NSUserDefaults standardUserDefaults] boolForKey:UDKeyApolloShowUnreadComments] == NO) {
return;
}
RDKComment *comment = MSHookIvar<RDKComment *>(self, "comment");
if (comment) {
NSDate *createdUTC = MSHookIvar<NSDate *>(comment, "_createdUTC");
UIView *view = MSHookIvar<UIView *>(self, "_view");
NSString *linkIDWithoutPrefix = [comment linkIDWithoutTypePrefix];

if (linkIDWithoutPrefix) {
NSDate *timestamp = [postSnapshots objectForKey:linkIDWithoutPrefix];
// Highlight if comment is newer than the timestamp saved in postSnapshots
if (view && createdUTC && timestamp && [createdUTC compare:timestamp] == NSOrderedDescending) {
UIView *yellowTintView = [[UIView alloc] initWithFrame: [view bounds]];
yellowTintView.backgroundColor = NewPostCommentsColor;
yellowTintView.userInteractionEnabled = NO;
[view insertSubview:yellowTintView atIndex:1];
}
}
}
}

- (void)linkButtonTappedWithSender:(_TtC6Apollo14LinkButtonNode *)arg1 {
%log;
NSURL *url = MSHookIvar<NSURL *>(arg1, "url");
Expand Down Expand Up @@ -582,32 +617,101 @@ static NSString *imageID;

%end

static void initializePostSnapshots(NSData *data) {
NSError *error = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error) {
return;
}
[postSnapshots removeAllObjects];
for (NSUInteger i = 0; i < jsonArray.count; i += 2) {
if ([jsonArray[i] isKindOfClass:[NSString class]] &&
[jsonArray[i + 1] isKindOfClass:[NSDictionary class]]) {

NSString *id = jsonArray[i];
NSDictionary *dict = jsonArray[i + 1];
NSTimeInterval timestamp = [dict[@"timestamp"] doubleValue];

NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:timestamp];
postSnapshots[id] = date;
}
}
}

@interface ApolloTabBarController : UITabBarController
@end

%hook ApolloTabBarController

- (void)viewDidLoad {
%orig;
// Listen for changes to postSnapshots so we can update our internal dictionary
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:UDKeyApolloPostCommentsSnapshots
options:NSKeyValueObservingOptionNew
context:NULL];
}

- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context {
if ([keyPath isEqual:UDKeyApolloPostCommentsSnapshots]) {
NSData *postSnapshotData = [[NSUserDefaults standardUserDefaults] objectForKey:UDKeyApolloPostCommentsSnapshots];
if (postSnapshotData) {
initializePostSnapshots(postSnapshotData);
}
}
}

- (void) dealloc {
%orig;
[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:UDKeyApolloPostCommentsSnapshots];
}

%end

%ctor {
cache = [NSCache new];
postSnapshots = [NSMutableDictionary dictionary];

NSError *error = NULL;
ShareLinkRegex = [NSRegularExpression regularExpressionWithPattern:ShareLinkRegexPattern options:NSRegularExpressionCaseInsensitive error:&error];
MediaShareLinkRegex = [NSRegularExpression regularExpressionWithPattern:MediaShareLinkPattern options:NSRegularExpressionCaseInsensitive error:&error];

NSDictionary *defaultValues = @{UDKeyBlockAnnouncements: @YES};
NSDictionary *defaultValues = @{UDKeyBlockAnnouncements: @YES, UDKeyEnableFLEX: @NO, UDKeyApolloShowUnreadComments: @NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];

sRedditClientId = (NSString *)[[[NSUserDefaults standardUserDefaults] objectForKey:UDKeyRedditClientId] ?: @"" copy];
sImgurClientId = (NSString *)[[[NSUserDefaults standardUserDefaults] objectForKey:UDKeyImgurClientId] ?: @"" copy];
sBlockAnnouncements = [[NSUserDefaults standardUserDefaults] boolForKey:UDKeyBlockAnnouncements];

%init(SettingsGeneralViewController=objc_getClass("Apollo.SettingsGeneralViewController"));
%init(SettingsGeneralViewController=objc_getClass("Apollo.SettingsGeneralViewController"), ApolloTabBarController=objc_getClass("Apollo.ApolloTabBarController"));

// Suppress wallpaper prompt
NSDate *dateIn90d = [NSDate dateWithTimeIntervalSinceNow:60*60*24*90];
[[NSUserDefaults standardUserDefaults] setObject:dateIn90d forKey:@"WallpaperPromptMostRecent2"];

// Disable subreddit weather time - broken
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"ShowSubredditWeatherTime"];

// Sideload fixes
rebind_symbols((struct rebinding[3]) {
{"SecItemAdd", (void *)SecItemAdd_replacement, (void **)&SecItemAdd_orig},
{"SecItemCopyMatching", (void *)SecItemCopyMatching_replacement, (void **)&SecItemCopyMatching_orig},
{"SecItemUpdate", (void *)SecItemUpdate_replacement, (void **)&SecItemUpdate_orig}
}, 3);

if ([[NSUserDefaults standardUserDefaults] boolForKey:UDKeyEnableFLEX]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[%c(FLEXManager) performSelector:@selector(sharedManager)] performSelector:@selector(showExplorer)];
});
}

NSData *postSnapshotData = [[NSUserDefaults standardUserDefaults] objectForKey:UDKeyApolloPostCommentsSnapshots];
if (postSnapshotData) {
initializePostSnapshots(postSnapshotData);
} else {
NSLog(@"No data found in NSUserDefaults for key 'PostCommentsSnapshots'");
}

// Redirect user to Custom API modal if no API credentials are set
if ([sRedditClientId length] == 0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
Expand Down
1 change: 1 addition & 0 deletions Tweaks/FLEXing
Submodule FLEXing added at 8373b7
20 changes: 19 additions & 1 deletion UserDefaultConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@
static NSString *const UDKeyRedditClientId = @"RedditApiClientId";
static NSString *const UDKeyImgurClientId = @"ImgurApiClientId";
static NSString *const UDKeyBlockAnnouncements = @"DisableApollonouncements";
static NSString *const UDKeyEnableFLEX = @"EnableFlexDebugging";

static NSString *const UDKeyApolloSubredditWeather = @"ShowSubredditWeatherTime";
static NSString *const UDKeyApolloShowUnreadComments = @"ShowUnreadComments";

/*
The UserDefaults key 'PostCommentsSnapshots' stores a snapshot JSON array of post IDs and their last-read timestamps and total comments:
[
"<post id 1>",
{
"timestamp": 726627090.96476996, // Reference date of January 2001
"totalComments": 442
},
"<post id 2>",
{
"timestamp": 726627790.97460103,
"totalComments": 62
},
...
]
*/
static NSString *const UDKeyApolloPostCommentsSnapshots = @"PostCommentsSnapshots";
2 changes: 1 addition & 1 deletion control
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: ca.jeffrey.apollo-improvedcustomapi
Name: Apollo-ImprovedCustomApi
Version: 1.0.12
Version: 1.1.1
Architecture: iphoneos-arm
Description: Apollo for Reddit tweak with in-app configurable API keys
Maintainer: JeffreyCA
Expand Down

0 comments on commit df07612

Please sign in to comment.