Skip to content

Commit

Permalink
create API creds from Apollo
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanArbuckle committed Jul 1, 2023
1 parent bb2b2ff commit fcc5dba
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 4 deletions.
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ include $(THEOS)/makefiles/common.mk

TWEAK_NAME = ApolloCustomApiCredentials

ApolloCustomApiCredentials_FILES = Tweak.m
ApolloCustomApiCredentials_CFLAGS = -fobjc-arc
ApolloCustomApiCredentials_FILES = Tweak.m RedditAPICredentialViewController.m
ApolloCustomApiCredentials_FRAMEWORKS = UIKit WebKit
ApolloCustomApiCredentials_CFLAGS = -fobjc-arc -Wno-unguarded-availability-new

include $(THEOS_MAKE_PATH)/tweak.mk
9 changes: 9 additions & 0 deletions RedditAPICredentialViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>

@interface RedditAPICredentialViewController : UIViewController <WKNavigationDelegate>

@property (nonatomic, retain) UILabel *statusLabel;

@end

124 changes: 124 additions & 0 deletions RedditAPICredentialViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#import "RedditAPICredentialViewController.h"

@implementation RedditAPICredentialViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor lightGrayColor];

// If an account is already signed in to reddit, attempting to navigate to the developer apps page will redirect to the mobile homepage.
// Work around this by signing out of reddit
[[WKWebsiteDataStore defaultDataStore] fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
for (WKWebsiteDataRecord *record in records) {
if ([record.displayName containsString:@"reddit"]) {
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^void {}];
}
}
}];

self.statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, self.view.frame.size.width, 25)];
self.statusLabel.text = @"Log in and create API credentials";
self.statusLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:self.statusLabel];

CGRect webviewFrame = self.view.frame;
webviewFrame.size.height -= 80;
webviewFrame.origin.y = 80;

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKWebView *webView = [[WKWebView alloc] initWithFrame:webviewFrame configuration:config];
webView.navigationDelegate = self;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.reddit.com/prefs/apps"]]];
[self.view addSubview:webView];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {

if (![webView.URL.absoluteString containsString:@"https://www.reddit.com/prefs/apps"]) {
return;
}

// Inspect contents of the developer apps page -- looking for a client id
[self pageDidUpdate:webView];
}

-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

// User clicked the "create app" button
if ([navigationAction.request.URL.absoluteString isEqualToString:@"navigation://create-app"]) {

// Cancel the dummy navigation
decisionHandler(WKNavigationActionPolicyCancel);

// Wait a bit for the request to complete and then check the page contents for a client id
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self pageDidUpdate:webView];
});

return;
}

decisionHandler(WKNavigationActionPolicyAllow);
}

- (void)pageDidUpdate:(WKWebView *)webView {

// Grab the page's source
[webView evaluateJavaScript:@"document.body.innerHTML" completionHandler:^(id _Nullable value, NSError * _Nullable error) {
if (error) {
NSLog(@"eval js error: %@", error);
return;
}

NSString *pageSource = (NSString *)value;

// Look for existing app with the correct redirect_uri
NSString *redirectURI = @"apollo://reddit-oauth";
if ([pageSource containsString:redirectURI] && [pageSource containsString:@"installed app"]) {

// Found an app. Trim down to the client_id
NSString *trimmedPageSource = [pageSource substringFromIndex:[pageSource rangeOfString:redirectURI].location];
NSRange clientIDRange = [trimmedPageSource rangeOfString:@"name=\"client_id\" value=\""];
trimmedPageSource = [trimmedPageSource substringFromIndex:clientIDRange.location + clientIDRange.length];

NSString *clientID = [trimmedPageSource substringToIndex:[trimmedPageSource rangeOfString:@"\""].location];
[[NSUserDefaults standardUserDefaults] setValue:clientID forKey:@"ApolloRedditAPIClientID"];
self.statusLabel.text = [NSString stringWithFormat:@"client id: %@", clientID];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

if (self.presentingViewController.view.superview) {
UIWindow *containingWindow = (UIWindow *)self.presentingViewController.view.superview;
[self dismissViewControllerAnimated:YES completion:nil];
[containingWindow setHidden:YES];
}
});

}
else {

// An app needs to be created

// Detect when the (last step) "create app" button is clicked in the webview.
// Accomplished by adding another click listener to the element that navigates to a fake url. This navigation is caught (and cancelled) by a WKWebView delegate method
[webView evaluateJavaScript:@"function notifyAppButtonClicked() { window.location.href = \"navigation://create-app\"; } Array.from(document.getElementsByTagName('BUTTON')).slice(-1)[0].addEventListener('click', notifyAppButtonClicked, false);" completionHandler:nil];

// Open the App Creation form
[webView evaluateJavaScript:@"document.getElementById('create-app-button').click();" completionHandler:^(id _Nullable arg1, NSError * _Nullable error) {

// Populate all the fields
NSString *prefillFormJS = [NSString stringWithFormat:@"document.getElementById('app_type_installed').checked = true; \
Array.from(document.getElementsByName('description')).slice(-1)[0].value = \"i'm a fun reddit app\"; \
Array.from(document.getElementsByName('about_url')).slice(-1)[0].value = \"https://google.com\"; \
document.getElementById('redirect_uri').value = \"apollo://reddit-oauth\"; \
Array.from(document.getElementsByName('name')).slice(-1)[0].value = \"Some app %@\";", [[NSProcessInfo processInfo] globallyUniqueString]];
[webView evaluateJavaScript:prefillFormJS completionHandler:nil];
}];

self.statusLabel.text = @"select \"create app\"";
}
}];
}

@end
22 changes: 20 additions & 2 deletions Tweak.m
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "RedditAPICredentialViewController.h"

static NSString * const kRedditClientID = @"CLIENT_ID_GOES_HERE";
static NSString * const kImgurClientID = @"IMGUR_CLIENT_ID_GOES_HERE";
static NSString * const kImgurRapidAPIKey = @"RAPID_API_KEY_GOES_HERE";


__attribute__ ((constructor)) static void init(void) {

if (![[NSUserDefaults standardUserDefaults] valueForKey:@"ApolloRedditAPIClientID"]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

UIWindow *mainWindow = ((UIWindowScene *)UIApplication.sharedApplication.connectedScenes.anyObject).windows.firstObject;

UIWindow *window = [[UIWindow alloc] initWithFrame:mainWindow.frame];
[window makeKeyAndVisible];
[mainWindow addSubview:window];

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:[[UIViewController alloc] init]];
[window addSubview:navController.view];

RedditAPICredentialViewController *viewController = [[RedditAPICredentialViewController alloc] init];
// viewController.modalPresentationStyle = UIModalPresentationFullScreen;
[navController presentViewController:viewController animated:YES completion:nil];
});
}

Class _RDKOAuthCredential = objc_getClass("RDKOAuthCredential");
if (_RDKOAuthCredential) {

Method clientIdMethod = class_getInstanceMethod(_RDKOAuthCredential, sel_registerName("clientIdentifier"));
IMP replacementImp = imp_implementationWithBlock(^NSString *(id _self) {
return kRedditClientID;
return [[NSUserDefaults standardUserDefaults] valueForKey:@"ApolloRedditAPIClientID"];
});
method_setImplementation(clientIdMethod, replacementImp);
}
Expand Down

0 comments on commit fcc5dba

Please sign in to comment.