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.

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