-
Notifications
You must be signed in to change notification settings - Fork 11
/
reddit.dart
182 lines (158 loc) · 6.31 KB
/
reddit.dart
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
part of reddit;
class Reddit {
static final Uri _BASE_API_URI_PUBLIC = Uri.parse("http://www.reddit.com/");
static final Uri _BASE_API_URI_OAUTH2 =
Uri.parse("https://oauth.reddit.com/");
static final Uri _AUTH_ENDPOINT =
Uri.parse("https://www.reddit.com/api/v1/authorize");
static final Uri _TOKEN_ENDPOINT =
Uri.parse("https://www.reddit.com/api/v1/access_token");
static final Logger logger = new Logger("reddit");
Client _client;
Subreddit _front;
Reddit(Client this._client) : _oauthEnabled = false {
_front = new Subreddit._(this, null);
}
Subreddit get frontPage => _front;
Subreddit sub(String sub) => new Subreddit._(this, sub);
RedditUser user(String username) => new RedditUser._(this, username);
// FilterableQuery multi(String multiPath) => new FilterableQuery._(this, "api/multi/$multiPath", {}, ["expand_srs"]);
Multireddit multi(String user, String multiName) =>
new Multireddit._(this, user, multiName);
/* BROWSE SUBREDDITS */
/**
* Allowed filters are all Listing filters.
*/
Listing newSubreddits() => new Listing._(this, "subreddits/new", {});
/**
* Allowed filters are all Listing filters.
*/
Listing popularSubreddits() => new Listing._(this, "subreddits/popular", {});
/**
* Allowed filters are "omit".
*/
FilterableQuery recommendedSubreddits(Iterable<String> subs) =>
new FilterableQuery._(
this, "api/recommend/sr/${subs.join(",")}", {}, ["omit"]);
/**
* Allowed filters are all Listing filters.
*/
Listing searchSubreddits(String query) =>
new Listing._(this, "subreddits/search", {"q": query});
Query subredditsByTopic(String topic) =>
new Query._(this, "api/subreddits_by_topic", {"query": topic});
/**
* Allowed filters are "id", "limit", "url".
*/
FilterableQuery info() =>
new FilterableQuery._(this, "api/info", {}, ["id", "limit", "url"]);
/* AUTHENTICATION */
Uri _baseApiUri() =>
_oauthEnabled ? _BASE_API_URI_OAUTH2 : _BASE_API_URI_PUBLIC;
bool _oauthEnabled;
oauth2.AuthorizationCodeGrant _grant;
/**
* Start the OAuth2 setup.
*
* After calling this method, directly call [authFinish] for App-only auth or use the [authUrl] to get
* authorization information from the user and provide those to [authFinish].
*
* Using OAuth will be required after August 3, 2015:
* https://www.reddit.com/r/redditdev/comments/2ujhkr/important_api_licensing_terms_clarified/
*/
void authSetup(String identifier, String secret) {
if (_grant != null)
throw new StateError("Should not call this method twice");
if (_oauthEnabled) throw new StateError("OAuth2 is already enabled");
_grant = new oauth2.AuthorizationCodeGrant(
identifier, _AUTH_ENDPOINT, _TOKEN_ENDPOINT,
secret: secret, httpClient: _client);
}
/**
* Get the authorization URL to forward a user to to get auth information.
*/
Uri authUrl(redirectUrl, {List<String> scopes, String state}) {
if (_grant == null) throw new StateError("Should first call setupOAuth2");
if (_oauthEnabled) throw new StateError("OAuth2 is already enabled");
if (redirectUrl is String)
redirectUrl = Uri.parse(redirectUrl);
else if (redirectUrl is! Uri)
throw new ArgumentError(
"redirectUrl parameter must be either of type Uri or String");
return _grant.getAuthorizationUrl(redirectUrl,
scopes: scopes, state: state);
}
/**
* Finish the OAuth2 setup.
*
* There are several options:
*
* - App-only auth
* Use either no parameters or provide [username] and [password]. Note that not passing user information will
* often result in getting 503 errors. E.g.
*
* await reddit.authFinish(username: "sroose", password: "you wish");
* // or
* await reddit.authFinish();
*
* - User auth
* Provide either [response] or [code], depending on what you received from the authorization server.
* In most cases, the response from the server contains the code, but you can also just provide the complete
* response. E.g.
*
* await reddit.authFinish(code: "code_from_server");
* // or
* await reddit.authFinish(response: authServerResponse);
*
* The Reddit instance provided by the Future, is the same as the instance this method is invoked on.
*/
Future<Reddit> authFinish(
{Map response, String code, String username, String password}) async {
if (_grant == null) throw new StateError("Should first call setupOAuth2");
if (_oauthEnabled) throw new StateError("OAuth2 is already enabled");
// shortening stuff
Reddit withAuthClient(Client oauthClient) {
logger.info("OAuth2 setup successful.");
_client = oauthClient;
_oauthEnabled = true;
_grant = null;
return this;
}
if (response == null && code == null) {
// APP-ONLY AUTH
logger.info("Requesting a userless OAuth2 token at $_TOKEN_ENDPOINT");
DateTime startTime = new DateTime.now();
Response response = await _client.post(
_TOKEN_ENDPOINT.replace(
userInfo: "${_grant.identifier}:${_grant.secret}"),
body: {
"grant_type": "client_credentials",
"username": username == null ? "" : username,
"password": password == null ? "" : password,
"duration": "permanent"
});
logger.fine(
"Access token response: [${response.statusCode}] ${response.body}");
oauth2.Credentials credentials = handleAccessTokenResponse(
response, _TOKEN_ENDPOINT, startTime, ["*"], "");
oauth2.Client oauthClient = new oauth2.Client(credentials,
identifier: _grant.identifier,
secret: _grant.secret,
httpClient: _client);
return withAuthClient(oauthClient);
} else {
// USER-ENABLED AUTH
// (flow using AuthorizationCodeGrant from oauth2 package)
logger.info("Enabling user authentication.");
if (response != null && code == null) {
return withAuthClient(
await _grant.handleAuthorizationResponse(response));
} else if (code != null && response == null) {
return withAuthClient(await _grant.handleAuthorizationCode(code));
} else {
throw new ArgumentError(
"Only either of response and code should be provided.");
}
}
}
}