This repository has been archived by the owner on Jan 24, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathHBLOLinkOpenerHandler.m
332 lines (286 loc) · 14.4 KB
/
HBLOLinkOpenerHandler.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
* Edited by bensge for IMDb support.
* Netbot support by Aehmlo - Riposte requires to query ADN's API for user ID.
* If you want to implement this, go ahead, but I don't use it enough to.
* Twitter status support, Twitterrific support, and Cydia support by Aehmlo.
*/
#import "HBLOLinkOpenerHandler.h"
#import <UIKit/NSString+UIKitAdditions.h>
#import <Cephei/HBPreferences.h>
@implementation HBLOLinkOpenerHandler {
HBPreferences *_preferences;
}
- (instancetype)init {
self = [super init];
if (self) {
self.name = @"LinkOpener";
self.identifier = @"LinkOpener";
self.preferencesBundle = [NSBundle bundleWithPath:@"/Library/PreferenceBundles/LinkOpenerPrefs.bundle"];
self.preferencesClass = @"HBLOLinkOpenerListController";
_preferences = [HBPreferences preferencesForIdentifier:@"org.thebigboss.linkopener"];
}
return self;
}
- (id)openURL:(NSURL *)url sender:(NSString *)sender {
// do nothing if we aren’t enabled
if (![_preferences boolForKey:@"Enabled" default:YES]) {
return nil;
}
// special case for file:// urls: launch ifile or filza
if ([url.scheme isEqualToString:@"file"]) {
if (![_preferences boolForKey:@"File" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[@"ifile://%@" stringByAppendingString:url.path]],
[NSURL URLWithString:[@"filza:/%@" stringByAppendingString:url.path]]
];
}
// from here on, we assume http or https
if (![url.scheme isEqualToString:@"http"] && ![url.scheme isEqualToString:@"https"]) {
return nil;
}
if ([url.host isEqualToString:@"twitter.com"] || [url.host isEqualToString:@"mobile.twitter.com"] || [url.host isEqualToString:@"m.twitter.com"]) {
static NSArray *NonUsernamePaths;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// this interesting huge array of random words is the array of non-username paths provided by
// the twitter API. we *should* be getting the latest from the API but that seems like a lot
// of complexity for something that probably shouldn’t be changing that much
// fun fact: there used to be accounts with usernames in this list! @search was one that seems
// to have only recently been removed (as of 2017-04-14)
// last updated: 2017-04-14
NonUsernamePaths = @[
@"about", @"account", @"accounts", @"activity", @"all", @"announcements", @"anywhere",
@"api_rules", @"api_terms", @"apirules", @"apps", @"auth", @"badges", @"blog", @"business",
@"buttons", @"contacts", @"devices", @"direct_messages", @"download", @"downloads",
@"edit_announcements", @"faq", @"favorites", @"find_sources", @"find_users", @"followers",
@"following", @"friend_request", @"friendrequest", @"friends", @"goodies", @"help", @"home",
@"i", @"im_account", @"inbox", @"invitations", @"invite", @"jobs", @"list", @"login", @"logo",
@"logout", @"me", @"mentions", @"messages", @"mockview", @"newtwitter", @"notifications",
@"nudge", @"oauth", @"phoenix_search", @"positions", @"privacy", @"public_timeline",
@"related_tweets", @"replies", @"retweeted_of_mine", @"retweets", @"retweets_by_others",
@"rules", @"saved_searches", @"search", @"sent", @"sessions", @"settings", @"share",
@"signup", @"signin", @"similar_to", @"statistics", @"terms", @"tos", @"translate", @"trends",
@"tweetbutton", @"twttr", @"update_discoverability", @"users", @"welcome", @"who_to_follow",
@"widgets", @"zendesk_auth", @"media_signup"
];
});
if (url.pathComponents.count == 2 && ![NonUsernamePaths containsObject:url.pathComponents[1]]) {
// https://twitter.com/:username
if (![_preferences boolForKey:@"TwitterUser" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[@"tweetbot:///user_profile/" stringByAppendingString:url.pathComponents[1]]],
[NSURL URLWithString:[@"twitter://user?screen_name=" stringByAppendingString:url.pathComponents[1]]],
[NSURL URLWithString:[@"twitterrific:///profile?screen_name=" stringByAppendingString:url.pathComponents[1]]]
];
} else if (url.pathComponents.count == 4 && ([url.pathComponents[2] isEqualToString:@"status"] || [url.pathComponents[2] isEqualToString:@"statuses"])) {
// https://twitter.com/:username/status/:id
if (![_preferences boolForKey:@"TwitterStatus" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[NSString stringWithFormat:@"tweetbot://%@/status/%@", url.pathComponents[1], url.pathComponents[3]]],
[NSURL URLWithString:[@"twitterrific:///tweet?id=" stringByAppendingString:url.pathComponents[3]]],
[NSURL URLWithString:[@"twitter:///status?id=" stringByAppendingString:url.pathComponents[3]]]
];
}
} else if ([url.host isEqualToString:@"www.facebook.com"] || [url.host isEqualToString:@"facebook.com"] || [url.host isEqualToString:@"fb.com"]) {
// https://www.facebook.com/:username
// TODO: we don’t check for non-username paths
if (url.pathComponents.count == 2) {
if (![_preferences boolForKey:@"FacebookUser" default:YES]) {
return nil;
}
return [NSURL URLWithString:[@"fb://profileForLinkOpener/" stringByAppendingString:url.pathComponents[1]]];
}
} else if ([url.host isEqualToString:@"imdb.com"] || [url.host isEqualToString:@"www.imdb.com"]) {
// http://www.imdb.com/title/:id
if (url.pathComponents.count == 3) {
if (![_preferences boolForKey:@"IMDBTitle" default:YES]) {
return nil;
}
return [NSURL URLWithString:[@"imdb:///title/" stringByAppendingString:url.pathComponents[2]]];
}
} else if ([url.host hasPrefix:@"ebay.co"] || [url.host hasPrefix:@"www.ebay.co"]) {
if (url.pathComponents.count > 2 && [url.pathComponents[1] isEqualToString:@"itm"]) {
// http://www.ebay.com/itm/:id/ or http://www.ebay.com/itm/:name/:id/
if (![_preferences boolForKey:@"EBayListing" default:YES]) {
return nil;
}
NSInteger index = url.pathComponents.count == 4 ? 3 : 2;
return [NSURL URLWithString:[@"ebay://launch?itm=" stringByAppendingString:url.pathComponents[index]]];
} else if (url.pathComponents.count > 3 && [url.pathComponents[1] isEqualToString:@"sch"]) {
// http://www.ebay.com.au/sch/i.html?_nkw=:query (ew, what decade is this?)
if (![_preferences boolForKey:@"EBaySearch" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"ebay://%@%@", url.host, url.path]];
}
} else if ([url.host isEqualToString:@"cydia.saurik.com"]) {
if (url.pathComponents.count == 3 && [url.pathComponents[1] isEqualToString:@"package"]) {
// https://cydia.saurik.com/package/:id/
if (![_preferences boolForKey:@"CydiaPackage" default:YES]) {
return nil;
}
return [NSURL URLWithString:[@"cydia://package/" stringByAppendingString:url.pathComponents[2]]];
}
} else if ([url.host isEqualToString:@"github.com"] || [url.host isEqualToString:@"gist.github.com"]) {
// all github.com and gist.github.com
if ([url.host isEqualToString:@"github.com"] && ![_preferences boolForKey:@"GitHubDotCom" default:YES]) {
return nil;
} else if ([url.host isEqualToString:@"gist.github.com"] && ![_preferences boolForKey:@"GitHubGist" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"ioc://%@%@", url.host, url.path]];
} else if ((([url.host isEqualToString:@"reddit.com"] || [url.host hasSuffix:@".reddit.com"]) && ([url.pathComponents containsObject:@"comments"] || url.pathComponents.count == 3)) || [url.host isEqualToString:@"redd.it"]) {
// *groan*
NSString *threadID = nil;
if ([url.host isEqualToString:@"redd.it"] && url.pathComponents.count == 2) {
// http://redd.it/:thread
if (![_preferences boolForKey:@"RedditShortener" default:YES]) {
return nil;
}
threadID = url.pathComponents[1];
} else if ((url.pathComponents.count == 5 || url.pathComponents.count == 6) && [url.pathComponents[2] isEqualToString:@"r"] && [url.pathComponents containsObject:@"comments"]) {
// https://www.reddit.com/r/:subreddit/comments/:thread/:name?/
threadID = url.pathComponents[4];
}
if (threadID) {
if (![_preferences boolForKey:@"RedditThread" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[NSString stringWithFormat:@"submarine://%@", url.absoluteString]],
[NSURL URLWithString:[NSString stringWithFormat:@"alienblue://thread/%@", threadID]]
];
} else if (url.pathComponents.count == 3 && [url.pathComponents[1] isEqualToString:@"r"]) {
// https://www.reddit.com/r/:subreddit
if (![_preferences boolForKey:@"RedditSubreddit" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[NSString stringWithFormat:@"submarine://subreddit/%@", url.pathComponents[2]]],
[NSURL URLWithString:[NSString stringWithFormat:@"alienblue://r/%@", url.pathComponents[2]]]
];
} else if (url.pathComponents.count > 2 && ([url.pathComponents[1] isEqualToString:@"u"] || [url.pathComponents[1] isEqualToString:@"user"])) {
// https://www.reddit.com/user/:username (or /u/)
if (![_preferences boolForKey:@"RedditUser" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"submarine://user/%@", url.pathComponents[2]]];
}
if (![_preferences boolForKey:@"RedditThread" default:YES]) {
return nil;
}
return [NSURL URLWithString:[@"alienblue://_linkopener_url?" stringByAppendingString:url.absoluteString]];
} else if ([url.host hasSuffix:@".tumblr.com"]) {
NSString *blog = [url.host componentsSeparatedByString:@"."][0];
if (url.pathComponents.count < 2) {
// https://:blog.tumblr.com/
if (![_preferences boolForKey:@"TumblrBlog" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"tumblr://x-callback-url/blog?blogName=%@", blog]];
} else if (url.pathComponents.count > 3 && [url.pathComponents[1] isEqualToString:@"post"]) {
// https://:blog.tumblr.com/post/:id/
if (![_preferences boolForKey:@"TumblrPost" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"tumblr://x-callback-url/blog?blogName=%@&postID=%@", blog, url.pathComponents[2]]];
}
} else if ([url.host isEqualToString:@"vine.co"]) {
if (url.pathComponents.count > 2) {
if ([url.pathComponents[1] isEqualToString:@"v"]) {
// https://vine.co/v/:id
if (![_preferences boolForKey:@"VineVideo" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"vine://post/%@", url.pathComponents[2]]];
} else if ([url.pathComponents[1] isEqualToString:@"u"]) {
// https://vine.co/u/:id
if (![_preferences boolForKey:@"VineUser" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"vine://user/%@", url.pathComponents[2]]];
}
}
} else if ([url.host isEqualToString:@"instagram.com"] || [url.host isEqualToString:@"www.instagram.com"]) {
if (url.pathComponents.count == 2) {
// https://instagram.com/:username
if (![_preferences boolForKey:@"InstagramUser" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"instagram://user?username=%@", url.pathComponents[2]]];
} else if (url.pathComponents.count == 3 && [url.pathComponents[1] isEqualToString:@"p"]) {
// https://instagram.com/p/:shortcode
if (![_preferences boolForKey:@"InstagramMedia" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"instagram://media?shortcode=%@", url.pathComponents[2]]];
} else if (url.pathComponents.count == 4 && [url.pathComponents[1] isEqualToString:@"explore"] && [url.pathComponents[2] isEqualToString:@"tags"]) {
// https://instagram.com/explore/tags/:tag
if (![_preferences boolForKey:@"InstagramTag" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"instagram://tag?name=%@", url.pathComponents[3]]];
} else if (url.pathComponents.count == 4 && [url.pathComponents[1] isEqualToString:@"explore"] && [url.pathComponents[2] isEqualToString:@"locations"]) {
// https://instagram.com/explore/locations/:id
if (![_preferences boolForKey:@"InstagramLocation" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"instagram://location?id=%@", url.pathComponents[3]]];
}
} else if ([url.host isEqualToString:@"dict.cc"] || [url.host hasSuffix:@".dict.cc"]) {
if (url.pathComponents.count < 2) {
NSDictionary *query = url.query.queryKeysAndValues;
if (query[@"s"]) {
// http://dict.cc/?s=:query
if (![_preferences boolForKey:@"DictccTranslation" default:YES]) {
return nil;
}
return @[
[NSURL URLWithString:[NSString stringWithFormat:@"dictcc-x-callback://x-callback-url/translate?word=%@", query[@"s"]]],
[NSURL URLWithString:[NSString stringWithFormat:@"dictccplus-x-callback://x-callback-url/translate?word=%@", query[@"s"]]]
];
}
}
} else if ([url.host isEqualToString:@"yelp.com"] || [url.host isEqualToString:@"www.yelp.com"]) {
static NSArray *SupportedPaths;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// if pathComponent[1] matches one of these, the app supports it
SupportedPaths = @[ @"search", @"biz", @"check_in", @"check_ins" ];
});
if (url.pathComponents.count > 2 && [SupportedPaths containsObject:url.pathComponents[1]]) {
// https://yelp.com/search/?find_desc=:query, etc
if (![_preferences boolForKey:@"YelpAll" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"yelp5.3://%@", url.path]];
}
} else if ([url.host isEqualToString:@"overcast.fm"]) {
if (url.pathComponents.count == 2 && [url.pathComponents[1] hasPrefix:@"+"]) {
// https://overcast.fm/+:episode
if (![_preferences boolForKey:@"OvercastEpisode" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"overcast://open%@", url.path]];
} else if (url.pathComponents.count == 2 || url.pathComponents.count == 3) {
// https://overcast.fm/:podcast/:name?
// this regex is based on the assumption that all podcast IDs will look like these two really
// specific cases, which i’m hoping holds true forever :p
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^itunes\\d+$|^p\\d+-[\\w\\d]+$" options:NSRegularExpressionCaseInsensitive error:nil];
if ([regex numberOfMatchesInString:url.pathComponents[1] options:kNilOptions range:NSMakeRange(0, url.pathComponents[1].length)] == 1) {
if (![_preferences boolForKey:@"OvercastPodcast" default:YES]) {
return nil;
}
return [NSURL URLWithString:[NSString stringWithFormat:@"overcast://open%@", url.path]];
}
}
}
return nil;
}
@end