diff --git a/Makefile b/Makefile index 3873cbd..c2a0dc4 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -ARCHS = armv7 armv7s arm64 arm64e +ARCHS = arm64 include $(THEOS)/makefiles/common.mk TWEAK_NAME = tfdidthatsay -tfdidthatsay_FILES = Tweak.xm +tfdidthatsay_FILES = $(wildcard tweak/*.xm) #tfdidthatsay_CFLAGS = -fobjc-arc include $(THEOS_MAKE_PATH)/tweak.mk diff --git a/control b/control index 5a5df97..1347942 100644 --- a/control +++ b/control @@ -1,7 +1,7 @@ Package: com.lint.undelete Name: TFDidThatSay? Depends: mobilesubstrate -Version: 1.1.0 +Version: 1.2.0 Architecture: iphoneos-arm Description: See "[deleted]" comments and posts without leaving Reddit! Maintainer: lint diff --git a/tfdidthatsay.plist b/tfdidthatsay.plist index 5849087..be1e7da 100644 --- a/tfdidthatsay.plist +++ b/tfdidthatsay.plist @@ -1 +1,8 @@ -{ Filter = { Bundles = ( "com.reddit.Reddit" ); }; } +{ + Filter = { + Bundles = ( + "com.reddit.Reddit", + "com.christianselig.Apollo" + ); + }; +} \ No newline at end of file diff --git a/tweak/Apollo.h b/tweak/Apollo.h new file mode 100644 index 0000000..301d6cf --- /dev/null +++ b/tweak/Apollo.h @@ -0,0 +1,66 @@ + +/* -- Comment Interfaces -- */ + +@interface RKComment +@property(assign,nonatomic) NSString* body; +@property(assign,nonatomic) NSString* bodyHTML; +@property(assign,nonatomic) NSString* author; +@property(assign,nonatomic) NSString* fullName; +@end + +@interface ApolloCommentCellNode +@property(assign,nonatomic)id view; +-(BOOL) isSelected; +-(void) _layoutSublayouts; +-(void) didLoad; +-(void) calculatedLayoutDidChange; + +//custom element +@property(assign,nonatomic) id undeleteButton; + +@end + +/* -- Post Interfaces -- */ + +@interface RKLink +@property(assign,nonatomic) NSString* selfText; +@property(assign,nonatomic) NSString* author; +@property(assign,nonatomic) NSString* fullName; + +-(BOOL) isSelfPost; +@end + +@interface ApolloCommentsHeaderCellNode +@property(assign, nonatomic) id undeleteButton; +@end + +/* -- Other Interfaces -- */ + +@interface MarkdownRenderer ++(id) attributedStringFromMarkdown:(id) arg1 withAttributes:(id) arg2; +@end + +/* -- ASyncDisplayKit Interfaces -- */ + +@interface _ASDisplayView : UIView +@end + +@interface ASImageNode +@property(assign,nonatomic)id image; +@property(assign,nonatomic) CGRect frame; +@property(assign,nonatomic) id view; +-(CGRect)_frameInWindow; +@end + +@interface ASTextNode +@property(assign,nonatomic) CGRect frame; +@property(assign,nonatomic) id attributedString; +@property(assign,nonatomic) id attributedText; +@end + +@interface ApolloApolloButtonNode +@property(assign,nonatomic) NSArray* subnodes; +@end + + + diff --git a/tweak/Apollo.xm b/tweak/Apollo.xm new file mode 100644 index 0000000..5c84b5b --- /dev/null +++ b/tweak/Apollo.xm @@ -0,0 +1,219 @@ + +#import "Apollo.h" + +%group Apollo + +%hook ApolloApolloButtonNode +%end + + +NSDictionary* apolloBodyAttributes = nil; + + +%hook RKComment + +-(BOOL) isDeleted{ + return NO; +} +%end + + +%hook MarkdownRenderer + ++(id) attributedStringFromHTML:(id)arg1 attributes:(id) arg2 compact:(BOOL) arg3{ + + apolloBodyAttributes = [arg2 copy]; + + return %orig; +} +%end + + +%hook ApolloCommentCellNode +%property(assign,nonatomic) id undeleteButton; + +%new +-(void) didTapUndeleteButton{ + + 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"]; + + [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]; + + author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; + body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"]; + + if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ + body = @"[comment was unable to be archived]"; + } + + } else if (error != nil || data == nil){ + body = @"[an error occured]"; + } + + 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]]; + + }]; +} + +-(void) didLoad { + %orig; + + 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; + + 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(assign, nonatomic) id undeleteButton; + +%new +-(void) didTapUndeleteButton{ + + 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"]; + + [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]; + + author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; + body = [[jsonData objectForKey:@"data"][0] objectForKey:@"selftext"]; + + if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ + body = @"[comment was unable to be archived]"; + } + + } else if (error != nil || data == nil){ + body = @"[an error occured]"; + } + + //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]]; + + }]; + +} + +-(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 + + +%ctor { + NSString* processName = [[NSProcessInfo processInfo] processName]; + + if ([processName isEqualToString:@"Apollo"]){ + %init(Apollo, ApolloCommentsHeaderCellNode = objc_getClass("Apollo.CommentsHeaderCellNode"), ApolloCommentCellNode = objc_getClass("Apollo.CommentCellNode"), ApolloApolloButtonNode = objc_getClass("Apollo.ApolloButtonNode")); + } +} + diff --git a/Tweak.h b/tweak/Reddit.h similarity index 76% rename from Tweak.h rename to tweak/Reddit.h index 7819ee7..d3cdace 100644 --- a/Tweak.h +++ b/tweak/Reddit.h @@ -1,20 +1,15 @@ +/* ---- Reddit v4 ---- */ + /* -- Comment Interfaces -- */ - -@interface Comment -@property(assign,nonatomic) id bodyRichTextAttributed; -@property(assign,nonatomic) id pk; -@end - @interface CommentTreeNode @property(assign,nonatomic) id comment; //custom elements @property(assign,nonatomic) id commentTreeHeaderNode; @property(assign,nonatomic) id commentTreeCommandBarNode; -@property(assign,nonatomic) BOOL isLoadingArchivedComment; @end @interface CommentTreeDisplayNode @@ -44,10 +39,8 @@ -(id)animationControllerForDismissedController:(id) arg1; @end - /* -- Post Interfaces -- */ - @interface Post @property(assign,nonatomic) id author; @property(assign,nonatomic) BOOL isSelfPost; @@ -118,22 +111,58 @@ +(id) sharedSettings; @end - -/* -- ActivityIndicator Interfaces -- */ - - @interface AccountManager @property(assign,nonatomic) id defaults; +(id) sharedManager; +@end + +/* ---- Reddit v3 ---- */ + + +/* -- Comment Interfaces -- */ + +@interface CommentCell : UIView +-(id) delegate; +-(id) comment; +-(id) commentView; @end -@interface _ASCollectionViewCell -@property(assign,nonatomic) id node; +@interface CommentView +-(void) configureSubviews; +-(void) layoutSubviews; +-(id) commandView; +-(id) comment; +-(id) delegate; @end -@interface CellDisplayNodeWrapper -@property(assign,nonatomic) id contentNode; +@interface CommentCommandView +@property (nonatomic, assign) id undeleteButton; +-(id)overflowButton; +-(id) comment; +-(id) delegate; +@end + +@interface CommentsViewController +-(void) reloadCommentsWithNewCommentsHighlight:(BOOL) arg1 autoScroll:(BOOL) arg2 animated:(BOOL) arg3; +-(void)updateFloatingViews; +@end + +/* -- Other Interfaces -- */ + +@interface MarkDownParser ++(id)attributedStringFromMarkdownString:(id)arg1; @end +/* ---- Reddit v3 & v4 ---- */ + + +@interface Comment +//v4 +@property(assign,nonatomic) id bodyRichTextAttributed; +@property(assign,nonatomic) id pk; + +//v3 +-(id)pkWithoutPrefix; +@end diff --git a/Tweak.xm b/tweak/Reddit.xm similarity index 75% rename from Tweak.xm rename to tweak/Reddit.xm index 4f9ef2f..24e3ba8 100644 --- a/Tweak.xm +++ b/tweak/Reddit.xm @@ -1,12 +1,11 @@ -#include "Tweak.h" +#import "Reddit.h" %group Redditv4 %hook CommentTreeNode %property(assign,nonatomic)id commentTreeHeaderNode; %property(assign,nonatomic)id commentTreeCommandBarNode; -%property(assign,nonatomic)BOOL isLoadingArchivedComment; %end @@ -28,37 +27,10 @@ %orig; [[self commentTreeNode] setCommentTreeCommandBarNode:self]; - [[self commentTreeNode] setIsLoadingArchivedComment:NO]; } %end -/* -%hook ASCollectionView - --(id) dequeueReusableCellWithReuseIdentifier: (id) arg1 forIndexPath:(id) arg2{ - id orig = %orig; - - if ([orig isKindOfClass:[%c(_ASCollectionViewCell) class]]){ - - id node = [[orig node] contentNode]; - - if ([node isKindOfClass:[%c(CommentTreeDisplayNode) class]]) { - id commentNode = [node commentNode]; - - if ([commentNode isLoadingArchivedComment]){ - - //[[[commentNode commentTreeCommandBarNode] activityIndicator] startAnimating]; - - } - } - } - return orig; -} -%end -*/ - - %hook CommentActionSheetViewController -(void) setItems:(id) arg1{ @@ -87,20 +59,6 @@ id commentTreeNode = [self commentTreeNode]; id comment = [commentTreeNode comment]; - - [commentTreeNode setIsLoadingArchivedComment:YES]; - - /* - id isNightMode = [[[%c(AccountManager) sharedManager] defaults] objectForKey:@"kUseNightKey"]; - if (isNightMode){ - UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; - } else { - UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - } - [self setActivityIndicator:activityIndicator]; - [activityIndicator startAnimating]; - [sender addSubview:activityIndicator]; - */ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; @@ -147,9 +105,6 @@ }]; [bodyMutableAttributedText endEditing]; - - - [comment setValue:bodyMutableAttributedText forKey:@"bodyRichTextAttributed"]; @@ -159,11 +114,7 @@ [comment setValue:bodyMutableAttributedText forKey:@"bodyAttributedText"]; [[commentTreeNode commentTreeHeaderNode] updateContentViewsForData:comment]; - - - [commentTreeNode setIsLoadingArchivedComment:NO]; - //[activityIndicator stopAnimating]; - + [request release]; [queue release]; [bodyMutableAttributedText release]; @@ -294,18 +245,104 @@ +%group Redditv3 + +%hook CommentView + +%new +-(void) buttonAction { + + id commentsViewController = [self delegate]; + id comment = [self comment]; + + NSError* error; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body",[comment pkWithoutPrefix]]]]; + [request setHTTPMethod:@"GET"]; + + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; + + NSString *author = @"[author]"; + NSString *body = @"[body]"; + + if (data != nil && error == nil){ + + id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + + author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"]; + body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"]; + + if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){ + body = @"[comment was unable to be archived]"; + } + + } else if (error != nil || data == nil){ + body = @"[an error occured]"; + } + + [comment setValue:author forKey:@"author"]; + + [comment setValue:[%c(MarkDownParser) attributedStringFromMarkdownString: body] forKey:@"bodyAttributedText"]; + [comment setValue:body forKey:@"bodyText"]; + + [commentsViewController reloadCommentsWithNewCommentsHighlight:NO autoScroll:NO animated:NO]; + +} + + +-(id) initWithFrame:(id)arg1{ + id orig = %orig; + id commandView = [self commandView]; + + UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [undeleteButton addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]; + + UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"]; + + [undeleteButton setImage:undeleteImage forState:UIControlStateNormal]; + + [commandView setUndeleteButton:undeleteButton]; + [commandView addSubview:undeleteButton]; + + return orig; +} + + +%end + + +%hook CommentCommandView +%property (assign, nonatomic) id undeleteButton; + +-(void) layoutSubviews{ + %orig; + + UIButton *button = [self undeleteButton]; + + button.frame = CGRectMake([[self overflowButton ] frame].origin.x - 32, 0, 32, 32); + +} +%end + +%end + + + %ctor{ + NSString* processName = [[NSProcessInfo processInfo] processName]; NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; NSArray* versionArray = [version componentsSeparatedByString:@"."]; - - if ([versionArray[0] isEqualToString:@"4"]){ - %init(Redditv4); - } else if ([versionArray[0] isEqualToString:@"3"]) { - + + if ([processName isEqualToString:@"Reddit"]){ + if ([versionArray[0] isEqualToString:@"4"]){ + %init(Redditv4); + } else if ([versionArray[0] isEqualToString:@"3"]) { + %init(Redditv3); + } } - }