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.

547 lines
16KB

  1. #import "Apollo.h"
  2. #import "assets/TFHelper.h"
  3. static BOOL isEnabled;
  4. static BOOL isApolloEnabled;
  5. static BOOL isTFDeletedOnly;
  6. static CGFloat pushshiftRequestTimeoutValue;
  7. static BOOL shouldApolloHaveButton;
  8. %group Apollo
  9. NSDictionary* apolloBodyAttributes = nil;
  10. BOOL shouldAddUndeleteCell = NO;
  11. id apolloCommentCell;
  12. id apolloCommentController;
  13. BOOL shouldAddUndeleteCellForContext = NO;
  14. id apolloCommentCellForContext;
  15. id apolloCommentsControllerForContext;
  16. %hook ApolloButtonNode
  17. %end
  18. %hook IconActionTableViewCell
  19. %end
  20. %hook RKComment
  21. - (BOOL)isDeleted {
  22. return NO;
  23. }
  24. - (BOOL)isModeratorRemoved {
  25. return NO;
  26. }
  27. %end
  28. //1.8+, unsure why this is all I needed to add for 1.8 support, the rest of this still works even w/o changing RKComment and RKLink references
  29. %hook RDKComment
  30. - (BOOL)isDeleted {
  31. return NO;
  32. }
  33. - (BOOL)isModeratorRemoved {
  34. return NO;
  35. }
  36. %end
  37. %hook RKLink
  38. %property(strong, nonatomic) NSString *undeleteAuthor;
  39. - (id)author {
  40. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  41. }
  42. %end
  43. %hook RDKLink
  44. %property(strong, nonatomic) NSString *undeleteAuthor;
  45. - (id)author {
  46. return [self undeleteAuthor] ? [self undeleteAuthor] : %orig;
  47. }
  48. %end
  49. %hook MarkdownRenderer
  50. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 {
  51. apolloBodyAttributes = [arg2 copy];
  52. return %orig;
  53. }
  54. // fix for v1.10.6 no longer getting text attributes, meaning text color was not showing properly
  55. + (id)attributedStringFromHTML:(id)arg1 attributes:(id)arg2 compact:(BOOL)arg3 snoomojiMapping:(id)arg4 {
  56. apolloBodyAttributes = [arg2 copy];
  57. return %orig;
  58. }
  59. %end
  60. %hook ActionController
  61. - (id)tableView:(id)arg1 cellForRowAtIndexPath:(NSIndexPath *)arg2 {
  62. if (shouldAddUndeleteCell) {
  63. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1) {
  64. id undeleteCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell" forIndexPath:arg2];
  65. id prevCell = [arg1 dequeueReusableCellWithIdentifier:@"IconActionCell"]; // is this necessary?
  66. UIImageView *prevCellImageView = MSHookIvar<UIImageView *>(prevCell, "iconImageView");
  67. CGSize prevImageSize = [[prevCellImageView image] size];
  68. UIImage *undeleteImage;
  69. //if (@available(iOS 13.0, *)){
  70. if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"13.0")) {
  71. undeleteImage = [UIImage systemImageNamed:@"eye"];
  72. if (!undeleteImage){
  73. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  74. } else {
  75. CGSize squareSize = CGSizeMake(undeleteImage.size.width, undeleteImage.size.width);
  76. UIGraphicsBeginImageContextWithOptions(squareSize, NO, 0);
  77. [undeleteImage drawInRect:CGRectMake(0, (squareSize.height - undeleteImage.size.height) / 2, squareSize.width, undeleteImage.size.height)];
  78. undeleteImage = UIGraphicsGetImageFromCurrentImageContext();
  79. UIGraphicsEndImageContext();
  80. }
  81. } else {
  82. undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  83. }
  84. CGFloat undeleteImageSizeValue = prevImageSize.width > prevImageSize.height ? prevImageSize.width : prevImageSize.height;
  85. if (undeleteImageSizeValue == 0) {
  86. undeleteImageSizeValue = 25;
  87. }
  88. UIGraphicsBeginImageContextWithOptions(CGSizeMake(undeleteImageSizeValue, undeleteImageSizeValue), NO, 0);
  89. [undeleteImage drawInRect:CGRectMake(0, 0, undeleteImageSizeValue, undeleteImageSizeValue)];
  90. undeleteImage = [UIGraphicsGetImageFromCurrentImageContext() imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  91. UIGraphicsEndImageContext();
  92. UILabel *undeleteLabel = MSHookIvar<UILabel *>(undeleteCell, "actionTitleLabel");
  93. UIImageView *undeleteImageView = MSHookIvar<UIImageView *>(undeleteCell, "iconImageView");
  94. undeleteLabel.text = @"TF Did That Say?";
  95. undeleteImageView.image = undeleteImage;
  96. // fix undelete cell being green when moderator cell is present
  97. UIColor *contentColor = [UIColor colorWithRed:0.137 green:0.6 blue:1 alpha:1];
  98. [undeleteImageView setTintColor:contentColor];
  99. NSMutableAttributedString *newAttribuedText = [[NSMutableAttributedString alloc] initWithAttributedString:undeleteLabel.attributedText];
  100. [newAttribuedText addAttribute:NSForegroundColorAttributeName value:contentColor range:NSMakeRange(0, [newAttribuedText length])];
  101. undeleteLabel.attributedText = newAttribuedText;
  102. MSHookIvar<UIImageView *>(undeleteCell, "disclosureIndicator").image = nil;
  103. return undeleteCell;
  104. }
  105. }
  106. return %orig;
  107. }
  108. - (void)tableView:(id)arg1 didSelectRowAtIndexPath:(NSIndexPath *)arg2 {
  109. if (shouldAddUndeleteCell) {
  110. if ([arg2 row] == [self tableView:arg1 numberOfRowsInSection:0] - 1){
  111. if (apolloCommentCell) {
  112. [apolloCommentCell undeleteCellWasSelected];
  113. } else {
  114. [apolloCommentController undeleteCellWasSelected];
  115. }
  116. }
  117. }
  118. %orig;
  119. }
  120. - (NSInteger)tableView:(id)arg1 numberOfRowsInSection:(NSInteger)arg2 {
  121. if (shouldAddUndeleteCell){
  122. return %orig + 1;
  123. } else {
  124. return %orig;
  125. }
  126. }
  127. - (id)animationControllerForDismissedController:(id)arg1 {
  128. shouldAddUndeleteCell = NO;
  129. return %orig;
  130. }
  131. %end
  132. %hook CommentCellNode
  133. %property(strong,nonatomic) UIButton *undeleteButton;
  134. - (void)moreOptionsTappedWithSender:(id)arg1 {
  135. if (!shouldApolloHaveButton){
  136. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  137. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  138. shouldAddUndeleteCell = YES;
  139. apolloCommentCell = self;
  140. apolloCommentController = nil;
  141. }
  142. }
  143. %orig;
  144. }
  145. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  146. if (!shouldApolloHaveButton){
  147. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  148. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  149. shouldAddUndeleteCell = YES;
  150. apolloCommentCell = self;
  151. apolloCommentController = nil;
  152. }
  153. }
  154. %orig;
  155. }
  156. - (void)didLoad {
  157. %orig;
  158. if (shouldApolloHaveButton) {
  159. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  160. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  161. CGFloat imageSize = 20.0f;
  162. UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  163. [undeleteButton addTarget:self action:@selector(didTapUndeleteButton:) forControlEvents:UIControlEventTouchUpInside];
  164. undeleteButton.frame = CGRectMake(0, 0, imageSize, imageSize);
  165. UIImage* undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160dark.png"];
  166. [undeleteButton setImage:undeleteImage forState:UIControlStateNormal];
  167. [[self view] addSubview:undeleteButton];
  168. [self setUndeleteButton:undeleteButton];
  169. }
  170. } else {
  171. id actionDelegate = MSHookIvar<id>(self, "actionDelegate");
  172. [actionDelegate setCommentCellNode:self];
  173. }
  174. }
  175. - (void)_layoutSublayouts {
  176. %orig;
  177. if (shouldApolloHaveButton) {
  178. if ([self undeleteButton]) {
  179. CGFloat imageSize = 20.0f;
  180. id moreNode = MSHookIvar<id>(self, "moreOptionsNode");
  181. id ageNode = MSHookIvar<id>(self, "ageNode");
  182. CGRect nodeFrame = [moreNode frame];
  183. CGFloat centerHeight = (nodeFrame.size.height + nodeFrame.origin.y * 2) / 2.0f;
  184. CGFloat nodeSpacing = [ageNode frame].origin.x - nodeFrame.origin.x - nodeFrame.size.width;
  185. [[self undeleteButton] setFrame:CGRectMake(nodeFrame.origin.x - imageSize - nodeSpacing, centerHeight - (imageSize / 2), imageSize, imageSize)];
  186. }
  187. }
  188. }
  189. %new
  190. - (void)didTapUndeleteButton:(id)sender {
  191. [sender setEnabled:NO];
  192. id comment = MSHookIvar<id>(self, "comment");
  193. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:@{@"sender" : sender} completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  194. }
  195. %new
  196. - (void)undeleteCellWasSelected {
  197. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  198. [%c(TFHelper) getUndeleteDataWithID:[[comment fullName] componentsSeparatedByString:@"_"][1] isComment:YES timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeleteCommentAction:)];
  199. }
  200. %new
  201. - (void)completeUndeleteCommentAction:(NSDictionary *)data {
  202. RKComment *comment = MSHookIvar<RKComment *>(self, "comment");
  203. id bodyNode = MSHookIvar<id>(self, "bodyNode");
  204. id authorNode = MSHookIvar<id>(self, "authorNode");
  205. NSString *author = data[@"author"];
  206. NSString *body = data[@"body"];
  207. [comment setAuthor:author];
  208. [comment setBody:body];
  209. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  210. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  211. NSAttributedString *newAuthorAttributedString = [[NSAttributedString alloc] initWithString:author attributes:authorStringAttributes];
  212. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  213. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  214. if ([data objectForKey:@"sender"]) {
  215. [data[@"sender"] setEnabled:YES];
  216. }
  217. }
  218. %end
  219. %hook CommentsViewController
  220. %property(strong, nonatomic) id headerCellNode;
  221. - (void)moreOptionsBarButtonItemTappedWithSender:(id)arg1 {
  222. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  223. NSString *author = [post author];
  224. if ([post isSelfPost]) {
  225. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  226. shouldAddUndeleteCell = YES;
  227. apolloCommentCell = nil;
  228. apolloCommentController = self;
  229. }
  230. }
  231. %orig;
  232. }
  233. %new
  234. - (void)undeleteCellWasSelected {
  235. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  236. [%c(TFHelper) getUndeleteDataWithID:[[post fullName] componentsSeparatedByString:@"_"][1] isComment:NO timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeletePostAction:)];
  237. }
  238. %new
  239. - (void)completeUndeletePostAction:(NSDictionary *)data {
  240. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  241. id headerCellNode = [self headerCellNode];
  242. id bodyNode = MSHookIvar<id>(headerCellNode, "bodyNode");
  243. id postInfoNode = MSHookIvar<id>(headerCellNode, "postInfoNode");
  244. id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode");
  245. NSString *author = data[@"author"];
  246. NSString *authorTextString = [NSString stringWithFormat:@"by %@", author];
  247. NSString *body = data[@"body"];
  248. [post setUndeleteAuthor:author];
  249. [post setSelfText:body];
  250. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  251. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  252. NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:authorTextString attributes:authorStringAttributes];
  253. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  254. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  255. }
  256. %end
  257. %hook CommentsHeaderCellNode
  258. - (void)didLoad {
  259. %orig;
  260. [[self closestViewController] setHeaderCellNode:self];
  261. }
  262. - (void)_layoutSublayouts {
  263. %orig;
  264. [[self closestViewController] setHeaderCellNode:self];
  265. }
  266. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  267. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  268. NSString *author = [post author];
  269. if ([post isSelfPost]) {
  270. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]){
  271. shouldAddUndeleteCell = YES;
  272. apolloCommentCell = nil;
  273. apolloCommentController = self;
  274. }
  275. }
  276. %orig;
  277. }
  278. %end
  279. %hook UIMenu
  280. - (id)menuByReplacingChildren:(id)arg5 {
  281. if (shouldAddUndeleteCellForContext) {
  282. UIAction *action = [UIAction actionWithTitle:@"TF Did That Say?" image:[UIImage systemImageNamed:@"eye"] identifier:@"testident" handler:^(__kindof UIAction* _Nonnull action) {
  283. id commentCell = apolloCommentCellForContext;
  284. id commentsController = apolloCommentsControllerForContext;
  285. if (commentCell) {
  286. [commentCell undeleteCellWasSelected];
  287. } else {
  288. [commentsController undeleteCellWasSelected];
  289. }
  290. }];
  291. arg5 = [arg5 arrayByAddingObject:action];
  292. }
  293. return %orig;
  294. }
  295. %end
  296. %hook CommentSectionController
  297. %property(strong, nonatomic) id commentCellNode;
  298. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  299. if (!shouldApolloHaveButton) {
  300. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  301. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  302. shouldAddUndeleteCellForContext = YES;
  303. apolloCommentCellForContext = [self commentCellNode];
  304. apolloCommentsControllerForContext = nil;
  305. }
  306. }
  307. return %orig;
  308. }
  309. %new
  310. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  311. shouldAddUndeleteCellForContext = NO;
  312. apolloCommentCellForContext = nil;
  313. apolloCommentsControllerForContext = nil;
  314. }
  315. %end
  316. %hook CommentsHeaderSectionController
  317. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  318. id commentsController = MSHookIvar<id>(self, "viewController");
  319. RKLink *post = MSHookIvar<RKLink *>(commentsController, "link");
  320. NSString *author = [post author];
  321. if ([post isSelfPost]) {
  322. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  323. shouldAddUndeleteCellForContext = YES;
  324. apolloCommentCellForContext = nil;
  325. apolloCommentsControllerForContext = commentsController;
  326. }
  327. }
  328. return %orig;
  329. }
  330. %new
  331. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  332. shouldAddUndeleteCellForContext = NO;
  333. apolloCommentCellForContext = nil;
  334. apolloCommentsControllerForContext = nil;
  335. }
  336. %end
  337. %end
  338. static void loadPrefs(){
  339. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  340. if (prefs){
  341. isEnabled = [prefs objectForKey:@"isEnabled"] ? [[prefs objectForKey:@"isEnabled"] boolValue] : YES;
  342. isApolloEnabled = [prefs objectForKey:@"isApolloEnabled"] ? [[prefs objectForKey:@"isApolloEnabled"] boolValue] : YES;
  343. isTFDeletedOnly = [prefs objectForKey:@"isTFDeletedOnly"] ? [[prefs objectForKey:@"isTFDeletedOnly"] boolValue] : YES;
  344. pushshiftRequestTimeoutValue = [prefs objectForKey:@"requestTimeoutValue"] ? [[prefs objectForKey:@"requestTimeoutValue"] doubleValue] : 10;
  345. shouldApolloHaveButton = [prefs objectForKey:@"shouldApolloHaveButton"] ? [[prefs objectForKey:@"shouldApolloHaveButton"] boolValue] : NO;
  346. } else {
  347. isEnabled = YES;
  348. isApolloEnabled = YES;
  349. isTFDeletedOnly = YES;
  350. pushshiftRequestTimeoutValue = 10;
  351. shouldApolloHaveButton = NO;
  352. }
  353. }
  354. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  355. loadPrefs();
  356. }
  357. %ctor {
  358. loadPrefs();
  359. NSString* processName = [[NSProcessInfo processInfo] processName];
  360. if ([processName isEqualToString:@"Apollo"]){
  361. if (isApolloEnabled && isEnabled){
  362. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
  363. %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"), CommentSectionController = objc_getClass("Apollo.CommentSectionController"), CommentsHeaderSectionController = objc_getClass("Apollo.CommentsHeaderSectionController"));
  364. }
  365. }
  366. }