@@ -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 |
@@ -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 <apieceoflint@protonmail.com> |
@@ -1 +1,8 @@ | |||
{ Filter = { Bundles = ( "com.reddit.Reddit" ); }; } | |||
{ | |||
Filter = { | |||
Bundles = ( | |||
"com.reddit.Reddit", | |||
"com.christianselig.Apollo" | |||
); | |||
}; | |||
} |
@@ -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 | |||
@@ -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<id>(self, "bodyNode"); | |||
id authorNode = MSHookIvar<id>(self, "authorNode"); | |||
id authorTextNode = [authorNode subnodes][0]; | |||
id comment = MSHookIvar<id>(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<id>(self, "moreOptionsNode"); | |||
id ageNode = MSHookIvar<id>(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<id>(self, "bodyNode"); | |||
id postInfoNode = MSHookIvar<id>(self, "postInfoNode"); | |||
id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode"); | |||
id authorTextNode = [authorNode subnodes][0]; | |||
id post = MSHookIvar<id>(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<NSString*>(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<id>(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<id>(self, "postInfoNode"); | |||
id ageNode = MSHookIvar<id>(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")); | |||
} | |||
} | |||
@@ -1,12 +1,8 @@ | |||
/* -- Comment Interfaces -- */ | |||
/* ---- Reddit v4 ---- */ | |||
@interface Comment | |||
@property(assign,nonatomic) id bodyRichTextAttributed; | |||
@property(assign,nonatomic) id pk; | |||
@end | |||
/* -- Comment Interfaces -- */ | |||
@interface CommentTreeNode | |||
@property(assign,nonatomic) id comment; | |||
@@ -14,7 +10,6 @@ | |||
//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 CommentView | |||
-(void) configureSubviews; | |||
-(void) layoutSubviews; | |||
-(id) commandView; | |||
-(id) comment; | |||
-(id) delegate; | |||
@end | |||
@interface _ASCollectionViewCell | |||
@property(assign,nonatomic) id node; | |||
@interface CommentCommandView | |||
@property (nonatomic, assign) id undeleteButton; | |||
-(id)overflowButton; | |||
-(id) comment; | |||
-(id) delegate; | |||
@end | |||
@interface CellDisplayNodeWrapper | |||
@property(assign,nonatomic) id contentNode; | |||
@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 |
@@ -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); | |||
} | |||
} | |||
} | |||