Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Apollo.xm 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  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. @try {
  214. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  215. }
  216. @catch (NSException *exc) {
  217. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes isForPreview:NO]];
  218. }
  219. if ([data objectForKey:@"sender"]) {
  220. [data[@"sender"] setEnabled:YES];
  221. }
  222. }
  223. %end
  224. %hook CommentsViewController
  225. %property(strong, nonatomic) id headerCellNode;
  226. - (void)moreOptionsBarButtonItemTappedWithSender:(id)arg1 {
  227. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  228. NSString *author = [post author];
  229. if ([post isSelfPost]) {
  230. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  231. shouldAddUndeleteCell = YES;
  232. apolloCommentCell = nil;
  233. apolloCommentController = self;
  234. }
  235. }
  236. %orig;
  237. }
  238. %new
  239. - (void)undeleteCellWasSelected {
  240. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  241. [%c(TFHelper) getUndeleteDataWithID:[[post fullName] componentsSeparatedByString:@"_"][1] isComment:NO timeout:pushshiftRequestTimeoutValue extraData:nil completionTarget:self completionSelector:@selector(completeUndeletePostAction:)];
  242. }
  243. %new
  244. - (void)completeUndeletePostAction:(NSDictionary *)data {
  245. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  246. id headerCellNode = [self headerCellNode];
  247. id bodyNode = MSHookIvar<id>(headerCellNode, "bodyNode");
  248. id postInfoNode = MSHookIvar<id>(headerCellNode, "postInfoNode");
  249. id authorNode = MSHookIvar<id>(postInfoNode, "authorButtonNode");
  250. NSString *author = data[@"author"];
  251. NSString *authorTextString = [NSString stringWithFormat:@"by %@", author];
  252. NSString *body = data[@"body"];
  253. [post setUndeleteAuthor:author];
  254. [post setSelfText:body];
  255. NSAttributedString *prevAuthorAttributedString = [authorNode attributedTitleForState:UIControlStateNormal];
  256. NSDictionary *authorStringAttributes = [prevAuthorAttributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, [prevAuthorAttributedString length])];
  257. NSAttributedString* newAuthorAttributedString = [[NSAttributedString alloc] initWithString:authorTextString attributes:authorStringAttributes];
  258. [authorNode setAttributedTitle:newAuthorAttributedString forState:UIControlStateNormal];
  259. @try {
  260. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes]];
  261. }
  262. @catch (NSException *exc) {
  263. [bodyNode setAttributedString:[%c(MarkdownRenderer) attributedStringFromMarkdown:body withAttributes:apolloBodyAttributes isForPreview:NO]];
  264. }
  265. }
  266. %end
  267. %hook CommentsHeaderCellNode
  268. - (void)didLoad {
  269. %orig;
  270. [[self closestViewController] setHeaderCellNode:self];
  271. }
  272. - (void)_layoutSublayouts {
  273. %orig;
  274. [[self closestViewController] setHeaderCellNode:self];
  275. }
  276. - (void)longPressedWithGestureRecognizer:(id)arg1 {
  277. RKLink *post = MSHookIvar<RKLink *>(self, "link");
  278. NSString *author = [post author];
  279. if ([post isSelfPost]) {
  280. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]){
  281. shouldAddUndeleteCell = YES;
  282. apolloCommentCell = nil;
  283. apolloCommentController = self;
  284. }
  285. }
  286. %orig;
  287. }
  288. %end
  289. %hook UIMenu
  290. - (id)menuByReplacingChildren:(id)arg5 {
  291. if (shouldAddUndeleteCellForContext) {
  292. UIAction *action = [UIAction actionWithTitle:@"TF Did That Say?" image:[UIImage systemImageNamed:@"eye"] identifier:@"testident" handler:^(__kindof UIAction* _Nonnull action) {
  293. id commentCell = apolloCommentCellForContext;
  294. id commentsController = apolloCommentsControllerForContext;
  295. if (commentCell) {
  296. [commentCell undeleteCellWasSelected];
  297. } else {
  298. [commentsController undeleteCellWasSelected];
  299. }
  300. }];
  301. arg5 = [arg5 arrayByAddingObject:action];
  302. }
  303. return %orig;
  304. }
  305. %end
  306. %hook CommentSectionController
  307. %property(strong, nonatomic) id commentCellNode;
  308. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  309. if (!shouldApolloHaveButton) {
  310. NSString *author = [MSHookIvar<RKComment *>(self, "comment") author];
  311. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  312. shouldAddUndeleteCellForContext = YES;
  313. apolloCommentCellForContext = [self commentCellNode];
  314. apolloCommentsControllerForContext = nil;
  315. }
  316. }
  317. return %orig;
  318. }
  319. %new
  320. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  321. shouldAddUndeleteCellForContext = NO;
  322. apolloCommentCellForContext = nil;
  323. apolloCommentsControllerForContext = nil;
  324. }
  325. %end
  326. %hook CommentsHeaderSectionController
  327. - (id)contextMenuInteraction:(id)arg1 configurationForMenuAtLocation:(CGPoint)arg2 {
  328. id commentsController = MSHookIvar<id>(self, "viewController");
  329. RKLink *post = MSHookIvar<RKLink *>(commentsController, "link");
  330. NSString *author = [post author];
  331. if ([post isSelfPost]) {
  332. if ([%c(TFHelper) shouldShowUndeleteButtonWithInfo:author isDeletedOnly:isTFDeletedOnly]) {
  333. shouldAddUndeleteCellForContext = YES;
  334. apolloCommentCellForContext = nil;
  335. apolloCommentsControllerForContext = commentsController;
  336. }
  337. }
  338. return %orig;
  339. }
  340. %new
  341. - (void)contextMenuInteraction:(id)arg1 willEndForConfiguration:(id)arg2 animator:(id)arg3 {
  342. shouldAddUndeleteCellForContext = NO;
  343. apolloCommentCellForContext = nil;
  344. apolloCommentsControllerForContext = nil;
  345. }
  346. %end
  347. %end
  348. static void loadPrefs(){
  349. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  350. if (prefs){
  351. isEnabled = [prefs objectForKey:@"isEnabled"] ? [[prefs objectForKey:@"isEnabled"] boolValue] : YES;
  352. isApolloEnabled = [prefs objectForKey:@"isApolloEnabled"] ? [[prefs objectForKey:@"isApolloEnabled"] boolValue] : YES;
  353. isTFDeletedOnly = [prefs objectForKey:@"isTFDeletedOnly"] ? [[prefs objectForKey:@"isTFDeletedOnly"] boolValue] : YES;
  354. pushshiftRequestTimeoutValue = [prefs objectForKey:@"requestTimeoutValue"] ? [[prefs objectForKey:@"requestTimeoutValue"] doubleValue] : 10;
  355. shouldApolloHaveButton = [prefs objectForKey:@"shouldApolloHaveButton"] ? [[prefs objectForKey:@"shouldApolloHaveButton"] boolValue] : NO;
  356. } else {
  357. isEnabled = YES;
  358. isApolloEnabled = YES;
  359. isTFDeletedOnly = YES;
  360. pushshiftRequestTimeoutValue = 10;
  361. shouldApolloHaveButton = NO;
  362. }
  363. }
  364. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  365. loadPrefs();
  366. }
  367. %ctor {
  368. loadPrefs();
  369. NSString* processName = [[NSProcessInfo processInfo] processName];
  370. if ([processName isEqualToString:@"Apollo"]){
  371. if (isApolloEnabled && isEnabled){
  372. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
  373. %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"));
  374. }
  375. }
  376. }