#import "Apollo.h" static BOOL isApolloDeletedCommentsOnly; static BOOL isApolloEnabled; static CGFloat pushshiftRequestTimeoutValue; %group Apollo NSDictionary* apolloBodyAttributes = nil; %hook ApolloApolloButtonNode %end %hook RKComment -(BOOL) isDeleted{ return NO; } -(BOOL) isModeratorRemoved{ return NO; } %end %hook MarkdownRenderer +(id) attributedStringFromHTML:(id)arg1 attributes:(id) arg2 compact:(BOOL) arg3{ apolloBodyAttributes = [arg2 copy]; return %orig; } %end %hook ApolloCommentCellNode %property(strong,nonatomic) id undeleteButton; %new -(void) didTapUndeleteButton:(id) sender{ [sender setEnabled:NO]; id bodyNode = MSHookIvar(self, "bodyNode"); id authorNode = MSHookIvar(self, "authorNode"); id authorTextNode = [authorNode subnodes][0]; id comment = MSHookIvar(self, "comment"); NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body",[[comment fullName] componentsSeparatedByString:@"_"][1]]]]; [request setHTTPMethod:@"GET"]; [request setTimeoutInterval:pushshiftRequestTimeoutValue]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSString *author = @"[author]"; NSString *body = @"[body]"; if (data != nil && error == nil){ id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if ([[jsonData objectForKey:@"data"] count] != 0){ author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"]; if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ body = @"[pushshift was unable to archive this]"; } } else { body = @"[pushshift has not archived this yet]"; } } else if (error != nil || data == nil){ body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]]; } id prevAuthorAttributedString = [authorTextNode attributedString]; NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])]; NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes]; [authorTextNode setAttributedText:newAuthorAttributedString]; [authorTextNode setAttributedString:newAuthorAttributedString]; [comment setAuthor:author]; [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]]; [sender setEnabled:YES]; }]; } -(void) didLoad { %orig; id commentBody = [MSHookIvar(self, "comment") body]; if ((isApolloDeletedCommentsOnly && ([commentBody isEqualToString:@"[deleted]"] || [commentBody isEqualToString:@"[removed]"])) || !isApolloDeletedCommentsOnly) { CGFloat imageSize = 20.0f; UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside]; undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize); UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"]; [undeleteButton setImage:undeleteImage forState:UIControlStateNormal]; [[self view] addSubview:undeleteButton]; [self setUndeleteButton:undeleteButton]; } } -(void) _layoutSublayouts{ %orig; if ([self undeleteButton]){ CGFloat imageSize = 20.0f; id moreNode = MSHookIvar(self, "moreOptionsNode"); id ageNode = MSHookIvar(self, "ageNode"); CGRect nodeFrame = [moreNode frame]; CGFloat centerHeight = (nodeFrame.size.height + nodeFrame.origin.y * 2) / 2.0f; CGFloat nodeSpacing =[ageNode frame].origin.x - nodeFrame.origin.x - nodeFrame.size.width; [[self undeleteButton] setFrame:CGRectMake(nodeFrame.origin.x - imageSize - nodeSpacing, centerHeight - (imageSize / 2), imageSize, imageSize)]; } } %end %hook ApolloCommentsHeaderCellNode %property(strong, nonatomic) id undeleteButton; %new -(void) didTapUndeleteButton:(id) sender{ [sender setEnabled:NO]; id bodyNode = MSHookIvar(self, "bodyNode"); id postInfoNode = MSHookIvar(self, "postInfoNode"); id authorNode = MSHookIvar(postInfoNode, "authorButtonNode"); id authorTextNode = [authorNode subnodes][0]; id post = MSHookIvar(self, "link"); NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/submission/?ids=%@&fields=author,selftext",[[post fullName] componentsSeparatedByString:@"_"][1]]]]; [request setHTTPMethod:@"GET"]; [request setTimeoutInterval:pushshiftRequestTimeoutValue]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSString *author = @"[author]"; NSString *body = @"[body]"; if (data != nil && error == nil){ id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if ([[jsonData objectForKey:@"data"] count] != 0){ author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; body = [[jsonData objectForKey:@"data"][0] objectForKey:@"selftext"]; if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ body = @"[pushshift was unable to archive this]"; } } else { body = @"[pushshift has not archived this yet]"; } } else if (error != nil || data == nil){ body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]]; } //MSHookIvar(post, "_author") = author; //Crashes when clicking on author name. You will have to search the author name to go find the profile. author = [NSString stringWithFormat:@"by %@", author]; id prevAuthorAttributedString = [authorTextNode attributedString]; NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])]; NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes]; [authorTextNode setAttributedText:newAuthorAttributedString]; [authorTextNode setAttributedString:newAuthorAttributedString]; [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]]; [sender setEnabled:YES]; }]; } -(void) didLoad{ %orig; if ([MSHookIvar(self, "link") isSelfPost]){ CGFloat imageSize = 20.0f; UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside]; UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"]; [undeleteButton setImage:undeleteImage forState:UIControlStateNormal]; undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize); [[self view] addSubview:undeleteButton]; [self setUndeleteButton:undeleteButton]; } } -(void) _layoutSublayouts{ %orig; if ([self undeleteButton]){ CGFloat imageSize = 20.0f; id postInfoNode = MSHookIvar(self, "postInfoNode"); id ageNode = MSHookIvar(postInfoNode, "ageButtonNode"); CGFloat centerHeight = [postInfoNode frame].origin.y + ([ageNode frame].size.height + [ageNode frame].origin.y * 2) / 2.0f; CGFloat buttonXPos = [postInfoNode frame].origin.x + [postInfoNode frame].size.width - imageSize; [[self undeleteButton] setFrame:CGRectMake(buttonXPos, centerHeight - (imageSize / 2), imageSize, imageSize)]; } } %end %end static void loadPrefs(){ NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"]; if (prefs){ if ([prefs objectForKey:@"isApolloEnabled"] != nil) { isApolloEnabled = [[prefs objectForKey:@"isApolloEnabled"] boolValue]; } else { isApolloEnabled = YES; } if ([prefs objectForKey:@"requestTimeoutValue"] != nil){ pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue]; } else { pushshiftRequestTimeoutValue = 10; } if ([prefs objectForKey:@"isApolloDeletedCommentsOnly"] != nil) { isApolloDeletedCommentsOnly = [[prefs objectForKey:@"isApolloDeletedCommentsOnly"] boolValue]; } else { isApolloDeletedCommentsOnly = YES; } } else { isApolloEnabled = YES; pushshiftRequestTimeoutValue = 10; isApolloDeletedCommentsOnly = YES; } } static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { loadPrefs(); } %ctor { loadPrefs(); NSString* processName = [[NSProcessInfo processInfo] processName]; if ([processName isEqualToString:@"Apollo"]){ if (isApolloEnabled){ CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); %init(Apollo, ApolloCommentsHeaderCellNode = objc_getClass("Apollo.CommentsHeaderCellNode"), ApolloCommentCellNode = objc_getClass("Apollo.CommentCellNode"), ApolloApolloButtonNode = objc_getClass("Apollo.ApolloButtonNode")); } } }