You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
12KB

  1. #import "Apollo.h"
  2. #import "assets/TFHelper.h"
  3. static BOOL isTFDeletedOnly;
  4. static BOOL isApolloEnabled;
  5. static CGFloat pushshiftRequestTimeoutValue;
  6. static BOOL shouldApolloHaveButton;
  7. %group Apollo
  8. NSDictionary* apolloBodyAttributes = nil;
  9. BOOL shouldAddUndeleteCell = NO;
  10. id apolloCommentCell;
  11. id apolloCommentController;
  12. %hook ApolloButtonNode
  13. %end
  14. %hook IconActionTableViewCell
  15. %end
  16. %hook RKComment
  17. -(BOOL) isDeleted{
  18. return NO;
  19. }
  20. -(BOOL) isModeratorRemoved{
  21. return NO;
  22. }
  23. %end
  24. %hook RKLink
  25. %property(strong, nonatomic) NSString *undeleteAuthor;
  26. -(id) author{
  27. if ([self undeleteAuthor]){
  28. return [self undeleteAuthor];
  29. } else {
  30. return %orig;
  31. }
  32. }
  33. %end
  34. %hook MarkdownRenderer
  35. +(id) attributedStringFromHTML:(id)arg1 attributes:(id) arg2 compact:(BOOL) arg3{
  36. apolloBodyAttributes = [arg2 copy];
  37. return %orig;
  38. }
  39. %end
  40. %hook ActionController
  41. -(id) tableView:(id) arg1 cellForRowAtIndexPath:(NSIndexPath *) arg2{
  42. if (shouldAddUndeleteCell){
  43. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  44. id undeleteCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell" forIndexPath:arg2];
  45. id prevCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell"];
  46. UIImageView *prevCellImageView = MSHookIvar<UIImageView *>(prevCell, "iconImageView");
  47. CGSize prevImageSize = [[prevCellImageView image] size];
  48. UIImage *undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  49. CGFloat undeleteImageSizeValue = prevImageSize.width > prevImageSize.height ? prevImageSize.width : prevImageSize.height;
  50. if (undeleteImageSizeValue == 0){
  51. undeleteImageSizeValue = 25;
  52. }
  53. UIGraphicsBeginImageContextWithOptions(CGSizeMake(undeleteImageSizeValue, undeleteImageSizeValue), NO, 0);
  54. [undeleteImage drawInRect:CGRectMake(0, 0, undeleteImageSizeValue, undeleteImageSizeValue)];
  55. undeleteImage = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  56. UIGraphicsEndImageContext();
  57. UILabel *undeleteLabel = MSHookIvar<UILabel *>(undeleteCell, "actionTitleLabel");
  58. UIImageView *undeleteImageView = MSHookIvar<UIImageView *>(undeleteCell, "iconImageView");
  59. undeleteLabel.text = @"TF Did That Say?";
  60. undeleteImageView.image = undeleteImage;
  61. return undeleteCell;
  62. }
  63. }
  64. return %orig;
  65. }
  66. -(void) tableView:(id) arg1 didSelectRowAtIndexPath:(NSIndexPath *)arg2{
  67. if (shouldAddUndeleteCell){
  68. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  69. if (apolloCommentCell){
  70. [apolloCommentCell undeleteCellWasSelected];
  71. } else {
  72. [apolloCommentController undeleteCellWasSelected];
  73. }
  74. }
  75. }
  76. %orig;
  77. }
  78. -(NSInteger) tableView:(id) arg1 numberOfRowsInSection:(NSInteger) arg2{
  79. if (shouldAddUndeleteCell){
  80. return %orig + 1;
  81. } else {
  82. return %orig;
  83. }
  84. }
  85. -(id) animationControllerForDismissedController:(id) arg1{
  86. shouldAddUndeleteCell = NO;
  87. return %orig;
  88. }
  89. %end
  90. %hook CommentCellNode
  91. %property(strong,nonatomic) UIButton *undeleteButton;
  92. -(void) moreOptionsTappedWithSender:(id) arg1{
  93. if (!shouldApolloHaveButton){
  94. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  95. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  96. shouldAddUndeleteCell = YES;
  97. apolloCommentCell = self;
  98. apolloCommentController = nil;
  99. }
  100. }
  101. %orig;
  102. }
  103. -(void) longPressedWithGestureRecognizer:(id) arg1{
  104. if (!shouldApolloHaveButton){
  105. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  106. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  107. shouldAddUndeleteCell = YES;
  108. apolloCommentCell = self;
  109. apolloCommentController = nil;
  110. }
  111. }
  112. %orig;
  113. }
  114. -(void) didLoad {
  115. %orig;
  116. if (shouldApolloHaveButton){
  117. NSString *commentBody = [MSHookIvar<RKComment *>(self, "comment") body];
  118. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:commentBody isDeletedOnly:isTFDeletedOnly]){
  119. CGFloat imageSize = 20.0f;
  120. UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  121. [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside];
  122. undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize);
  123. UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  124. [undeleteButton setImage:undeleteImage forState:UIControlStateNormal];
  125. [[self view] addSubview:undeleteButton];
  126. [self setUndeleteButton:undeleteButton];
  127. }
  128. }
  129. }
  130. -(void) _layoutSublayouts{
  131. %orig;
  132. if (shouldApolloHaveButton){
  133. if ([self undeleteButton]){
  134. CGFloat imageSize = 20.0f;
  135. id moreNode = MSHookIvar<id>(self, "moreOptionsNode");
  136. id ageNode = MSHookIvar<id>(self, "ageNode");
  137. CGRect nodeFrame = [moreNode frame];
  138. CGFloat centerHeight = (nodeFrame.size.height + nodeFrame.origin.y * 2) / 2.0f;
  139. CGFloat nodeSpacing = [ageNode frame].origin.x - nodeFrame.origin.x - nodeFrame.size.width;
  140. [[self undeleteButton] setFrame:CGRectMake(nodeFrame.origin.x - imageSize - nodeSpacing, centerHeight - (imageSize / 2), imageSize, imageSize)];
  141. }
  142. }
  143. }
  144. %new
  145. -(void) didTapUndeleteButton:(id) sender{
  146. [sender setEnabled:NO];
  147. id comment = MSHookIvar<id>(self, "comment");
  148. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:@{@"sender" : sender} completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  149. }
  150. %new
  151. -(void) undeleteCellWasSelected{
  152. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  153. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  154. }
  155. %new
  156. -(void) completeUndeleteCommentAction:(NSDictionary *) data{
  157. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  158. id bodyNode = MSHookIvar<id>(self, "bodyNode");
  159. id authorNode = MSHookIvar<id>(self, "authorNode");
  160. NSString *author = data[@"author"];
  161. NSString *body = data[@"body"];
  162. [comment setAuthor:author];
  163. [comment setBody:body];
  164. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  165. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  166. NSAttributedString *newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes];
  167. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  168. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  169. if ([data objectForKey:@"sender"]) {
  170. [data[@"sender"] setEnabled:YES];
  171. }
  172. }
  173. %end
  174. %hook CommentsViewController
  175. %property(strong, nonatomic) id headerCellNode;
  176. -(void) moreOptionsBarButtonItemTappedWithSender:(id) arg1{
  177. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  178. NSString *postBody = [post selfText];
  179. if ([post isSelfPost]){
  180. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:postBody isDeletedOnly:isTFDeletedOnly]){
  181. shouldAddUndeleteCell = YES;
  182. apolloCommentCell = nil;
  183. apolloCommentController = self;
  184. }
  185. }
  186. %orig;
  187. }
  188. %new
  189. -(void) undeleteCellWasSelected{
  190. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  191. [%c(TFHelper) getUndeleteDataWithID:[[post fullName] componentsSeparatedByString:@"_"][1] isComment:NO timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeletePostAction:)];
  192. }
  193. %new
  194. -(void) completeUndeletePostAction:(NSDictionary *) data{
  195. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  196. id headerCellNode = [self headerCellNode];
  197. id bodyNode = MSHookIvar<id>(headerCellNode, "bodyNode");
  198. id postInfoNode = MSHookIvar<id>(headerCellNode, "postInfoNode");
  199. id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode");
  200. NSString *author = data[@"author"];
  201. NSString *authorTextString = [NSString stringWithFormat:@"by %@", author];
  202. NSString *body = data[@"body"];
  203. [post setUndeleteAuthor:author];
  204. [post setSelfText:body];
  205. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  206. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  207. NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:authorTextString attributes:authorStringAttributes];
  208. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  209. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  210. }
  211. %end
  212. %hook CommentsHeaderCellNode
  213. -(void) didLoad{
  214. %orig;
  215. [[self closestViewController] setHeaderCellNode:self];
  216. }
  217. -(void) _layoutSublayouts{
  218. %orig;
  219. [[self closestViewController] setHeaderCellNode:self];
  220. }
  221. -(void) longPressedWithGestureRecognizer:(id) arg1{
  222. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  223. NSString *postBody = [post selfText];
  224. if ([post isSelfPost]){
  225. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:postBody isDeletedOnly:isTFDeletedOnly]){
  226. shouldAddUndeleteCell = YES;
  227. apolloCommentCell = nil;
  228. apolloCommentController = self;
  229. }
  230. }
  231. %orig;
  232. }
  233. %end
  234. %end
  235. static void loadPrefs(){
  236. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  237. if (prefs){
  238. if ([prefs objectForKey:@"isApolloEnabled"] != nil) {
  239. isApolloEnabled = [[prefs objectForKey:@"isApolloEnabled"] boolValue];
  240. } else {
  241. isApolloEnabled = YES;
  242. }
  243. if ([prefs objectForKey:@"isTFDeletedOnly"] != nil) {
  244. isTFDeletedOnly = [[prefs objectForKey:@"isTFDeletedOnly"] boolValue];
  245. } else {
  246. isTFDeletedOnly = YES;
  247. }
  248. if ([prefs objectForKey:@"requestTimeoutValue"] != nil){
  249. pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue];
  250. } else {
  251. pushshiftRequestTimeoutValue = 10;
  252. }
  253. if ([prefs objectForKey:@"shouldApolloHaveButton"] != nil){
  254. shouldApolloHaveButton = [[prefs objectForKey:@"shouldApolloHaveButton"] boolValue];
  255. } else {
  256. shouldApolloHaveButton = NO;
  257. }
  258. } else {
  259. isApolloEnabled = YES;
  260. isTFDeletedOnly = YES;
  261. pushshiftRequestTimeoutValue = 10;
  262. shouldApolloHaveButton = NO;
  263. }
  264. }
  265. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  266. loadPrefs();
  267. }
  268. %ctor {
  269. loadPrefs();
  270. NSString* processName = [[NSProcessInfo processInfo] processName];
  271. if ([processName isEqualToString:@"Apollo"]){
  272. if (isApolloEnabled){
  273. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
  274. %init(Apollo, CommentsHeaderCellNode = objc_getClass("Apollo.CommentsHeaderCellNode"), CommentCellNode = objc_getClass("Apollo.CommentCellNode"), ApolloButtonNode = objc_getClass("Apollo.ApolloButtonNode"), ActionController = objc_getClass("Apollo.ActionController"), IconActionTableViewCell = objc_getClass("Apollo.IconActionTableViewCell"), CommentsViewController = objc_getClass("Apollo.CommentsViewController"));
  275. }
  276. }
  277. }