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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
use reqwest::Url;
use std::borrow::Cow;

use std::io;

macro_rules! endpoints {
    ($($name:ident => $ep:tt),*) => {
        $(pub const $name : EndpointBuilder = EndpointBuilder(Cow::Borrowed($ep));)*
    };
}

macro_rules! uri_segments {
    ($($name:ident),*) => {
        $(
        pub fn $name(&self, $name: &str) -> EndpointBuilder {
            self.replace(concat!("#", stringify!($name)), $name)
        }
        )*
    };
}

#[derive(Copy, Clone)]
pub enum SearchSort {
    Relevance,
    Hot,
    Top,
    New,
    Comments,
}

impl SearchSort {
    pub fn to_str(&self) -> &'static str {
        match self {
            SearchSort::Relevance => "relevance",
            SearchSort::Hot => "hot",
            SearchSort::Top => "top",
            SearchSort::New => "new",
            SearchSort::Comments => "comments",
        }
    }
}

pub enum EndpointBase {
    Regular,
    OAuth,
}
impl EndpointBase {
    pub fn get_str(&self) -> &str {
        match self {
            EndpointBase::Regular => "https://reddit.com",
            EndpointBase::OAuth => "https://oauth.reddit.com",
        }
    }
}

/// Build an endpoint without the base attached.
/// E.g. /r/rust/top
pub struct EndpointBuilder(Cow<'static, str>);

impl EndpointBuilder {
    pub fn new(ep: &str) -> Self {
        EndpointBuilder(Cow::Owned(ep.to_owned()))
    }

    fn replace(&self, needle: &str, haystack: &str) -> EndpointBuilder {
        EndpointBuilder(self.0.as_ref().replace(needle, haystack).into())
    }

    uri_segments! {
        subreddit,
        id,
        page,
        user
    }

    pub fn regular(self) -> io::Result<Endpoint> {
        Endpoint::new(EndpointBase::Regular, self)
    }
}

#[derive(Clone)]
pub struct Endpoint(Url);
impl Endpoint {
    pub fn new(base: EndpointBase, ep: EndpointBuilder) -> io::Result<Endpoint> {
        let ep_url = Url::parse(base.get_str())
            .unwrap()
            .join(ep.0.as_ref())
            .map_err(|_| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    format!("Invalid url: {}", ep.0),
                )
            })?
            .join(".json")
            .unwrap();
        Ok(Endpoint(ep_url))
    }

    pub fn build(ep_str: &str) -> EndpointBuilder {
        EndpointBuilder::new(ep_str)
    }

    pub fn to_url(&self) -> Url {
        self.0.clone()
    }

    pub fn filter(
        mut self,
        q: Option<&str>,
        sort: SearchSort,
        before: Option<&str>,
        after: Option<&str>,
    ) -> Endpoint {
        {
            let mut query = self.0.query_pairs_mut();
            query.append_pair("restrict_sr", "on");
            if let Some(search_string) = q {
                query.append_pair("q", search_string);
            }
            query.append_pair("sort", sort.to_str());

            if let Some(afer_thing) = after {
                query.append_pair("after", afer_thing.as_ref());
            }

            if let Some(before_thing) = before {
                query.append_pair("before", &before_thing.as_ref());
            }
        }
        self
    }
}

impl AsRef<str> for Endpoint {
    fn as_ref(&self) -> &str {
        self.0.as_ref()
    }
}

endpoints! {
    ABOUT_EDITED =>            "r/#subreddit/about/edited/",
    ABOUT_LOG =>               "r/#subreddit/about/log/",
    ABOUT_MODQUEUE =>          "r/#subreddit/about/modqueue/",
    ABOUT_REPORTS =>           "r/#subreddit/about/reports/",
    ABOUT_SPAM =>              "r/#subreddit/about/spam/",
    ABOUT_STICKY =>            "r/#subreddit/about/sticky/",
    ABOUT_STYLESHEET =>        "r/#subreddit/about/stylesheet/",
    ABOUT_TRAFFIC =>           "r/#subreddit/about/traffic/",
    ABOUT_UNMODERATED =>       "r/#subreddit/about/unmoderated/",
    ACCEPT_MOD_INVITE =>       "r/#subreddit/api/accept_moderator_invite/",
    ADD_SUBREDDIT_RULE =>      "api/add_subreddit_rule/",
    APPROVE =>                 "api/approve/",
    BLOCK =>                   "api/block/",
    BLOCK_USER =>              "/api/block_user/",
    BLOCKED =>                 "prefs/blocked/",
    COLLAPSE =>                "api/collapse_message/",
    COLLECTION =>              "api/v1/collections/collection/",
    COLLECTION_ADD_POST =>     "api/v1/collections/add_post_to_collection/",
    COLLECTION_CREATE =>       "api/v1/collections/create_collection/",
    COLLECTION_DELETE =>       "api/v1/collections/delete_collection/",
    COLLECTION_DESC =>         "api/v1/collections/update_collection_description/",
    COLLECTION_FOLLOW =>       "api/v1/collections/follow_collection/",
    COLLECTION_REMOVE_POST =>  "api/v1/collections/remove_post_in_collection/",
    COLLECTION_REORDER =>      "api/v1/collections/reorder_collection/",
    COLLECTION_SUBREDDIT =>    "api/v1/collections/subreddit_collections/",
    COLLECTION_TITLE =>        "api/v1/collections/update_collection_title/",
    COMMENT =>                 "api/comment/",
    COMMENT_REPLIES =>         "message/comments/",
    COMPOSE =>                 "api/compose/",
    CONTEST_MODE =>            "api/set_contest_mode/",
    DEL =>                     "api/del/",
    DELETE_MESSAGE =>          "api/del_msg/",
    DELETE_SR_BANNER =>        "r/#subreddit/api/delete_sr_banner/",
    DELETE_SR_HEADER =>        "r/#subreddit/api/delete_sr_header/",
    DELETE_SR_ICON =>          "r/#subreddit/api/delete_sr_icon/",
    DELETE_SR_IMAGE =>         "r/#subreddit/api/delete_sr_img/",
    DELETEFLAIR =>             "r/#subreddit/api/deleteflair/",
    DISTINGUISH =>             "api/distinguish/",
    DOMAIN =>                  "domain/#domain/",
    DUPLICATES =>              "duplicates/{submission_id}/",
    EDIT =>                    "api/editusertext/",
    EMOJI_DELETE =>            "api/v1/#subreddit/emoji/{emoji_name}",
    EMOJI_LEASE =>             "api/v1/#subreddit/emoji_asset_upload_s3.json/",
    EMOJI_LIST =>              "api/v1/#subreddit/emojis/all/",
    EMOJI_UPDATE =>            "api/v1/#subreddit/emoji_permissions/",
    EMOJI_UPLOAD =>            "api/v1/#subreddit/emoji.json/",
    FLAIR =>                   "r/#subreddit/api/flair/",
    FLAIRCONFIG =>             "r/#subreddit/api/flairconfig/",
    FLAIRCSV =>                "r/#subreddit/api/flaircsv/",
    FLAIRLIST =>               "r/#subreddit/api/flairlist/",
    FLAIRSELECTOR =>           "r/#subreddit/api/flairselector/",
    FLAIRTEMPLATE_V2 =>        "r/#subreddit/api/flairtemplate_v2",
    FLAIRTEMPLATECLEAR =>      "r/#subreddit/api/clearflairtemplates/",
    FLAIRTEMPLATEDELETE =>     "r/#subreddit/api/deleteflairtemplate/",
    FRIEND =>                  "r/#subreddit/api/friend/",
    FRIEND_V1 =>               "api/v1/me/friends/#user/",
    FRIENDS =>                 "api/v1/me/friends/",
    GILD_THING =>              "api/v1/gold/gild/#fullname/",
    GILD_USER =>               "api/v1/gold/give/#username/",
    HIDE =>                    "api/hide/",
    IGNORE_REPORTS =>          "api/ignore_reports/",
    INBOX =>                   "message/inbox/",
    INFO =>                    "api/info/",
    KARMA =>                   "api/v1/me/karma/",
    LEAVECONTRIBUTOR =>        "api/leavecontributor/",
    LINK_FLAIR =>              "r/#subreddit/api/link_flair_v2",
    LIST_BANNED =>             "r/#subreddit/about/banned/",
    LIST_CONTRIBUTOR =>        "r/#subreddit/about/contributors/",
    LIST_MODERATOR =>          "r/#subreddit/about/moderators/",
    LIST_MUTED =>              "r/#subreddit/about/muted/",
    LIST_WIKIBANNED =>         "r/#subreddit/about/wikibanned/",
    LIST_WIKICONTRIBUTOR =>    "r/#subreddit/about/wikicontributors/",
    LIVE_ACCEPT_INVITE =>      "api/live/#id/accept_contributor_invite/",
    LIVE_ADD_UPDATE =>         "api/live/#id/update/",
    LIVE_CLOSE =>              "api/live/#id/close_thread/",
    LIVE_CONTRIBUTORS =>       "live/#id/contributors/",
    LIVE_DISCUSSIONS =>        "live/#id/discussions/",
    LIVE_FOCUS =>              "live/{thread_id}/updates/{update_id}",
    LIVE_INFO =>               "api/live/by_id/#ids/",
    LIVE_INVITE =>             "api/live/#id/invite_contributor/",
    LIVE_LEAVE =>              "api/live/#id/leave_contributor/",
    LIVE_NOW =>                "api/live/happening_now/",
    LIVE_REMOVE_CONTRIB =>     "api/live/#id/rm_contributor/",
    LIVE_REMOVE_INVITE =>      "api/live/#id/rm_contributor_invite/",
    LIVE_REMOVE_UPDATE =>      "api/live/#id/delete_update/",
    LIVE_REPORT =>             "api/live/#id/report/",
    LIVE_STRIKE =>             "api/live/#id/strike_update/",
    LIVE_UPDATE_PERMS =>       "api/live/#id/set_contributor_permissions/",
    LIVE_UPDATE_THREAD =>      "api/live/#id/edit/",
    LIVE_UPDATES =>            "live/#id/",
    LIVEABOUT =>               "api/live/#id/about/",
    LIVECREATE =>              "api/live/create/",
    LOCK =>                    "api/lock/",
    MARKNSFW =>                "api/marknsfw/",
    ME =>                      "api/v1/me/",
    MEDIA_ASSET =>             "api/media/asset.json/",
    MENTIONS =>                "message/mentions/",
    MESSAGE =>                 "message/messages/#id/",
    MESSAGES =>                "message/messages/",
    MODERATED =>               "user/#user/moderated_subreddits/",
    MODERATOR_MESSAGES =>      "r/#subreddit/message/moderator/",
    MODERATOR_UNREAD =>        "r/#subreddit/message/moderator/unread/",
    MODMAIL_ARCHIVE =>         "api/mod/conversations/#id/archive/",
    MODMAIL_BULK_READ =>       "api/mod/conversations/bulk/read/",
    MODMAIL_CONVERSATION =>    "api/mod/conversations/#id/",
    MODMAIL_CONVERSATIONS =>   "api/mod/conversations/",
    MODMAIL_HIGHLIGHT =>       "api/mod/conversations/#id/highlight/",
    MODMAIL_MUTE =>            "api/mod/conversations/#id/mute/",
    MODMAIL_READ =>            "api/mod/conversations/read/",
    MODMAIL_SUBREDDITS =>      "api/mod/conversations/subreddits/",
    MODMAIL_UNARCHIVE =>       "api/mod/conversations/#id/unarchive/",
    MODMAIL_UNMUTE =>          "api/mod/conversations/#id/unmute/",
    MODMAIL_UNREAD =>          "api/mod/conversations/unread/",
    MODMAIL_UNREAD_COUNT =>    "api/mod/conversations/unread/count/",
    MORECHILDREN =>            "api/morechildren/",
    MULTIREDDIT =>             "user/#user/m/#multi/",
    MULTIREDDIT_API =>         "api/multi/user/#user/m/#multi/",
    MULTIREDDIT_BASE =>        "api/multi/",
    MULTIREDDIT_COPY =>        "api/multi/copy/",
    MULTIREDDIT_RENAME =>      "api/multi/rename/",
    MULTIREDDIT_UPDATE =>      "api/multi/user/#user/m/#multi/r/#subreddit/",
    MULTIREDDIT_USER =>        "api/multi/user/#user/",
    MUTE_SENDER =>             "api/mute_message_author/",
    MY_CONTRIBUTOR =>          "subreddits/mine/contributor/",
    MY_MODERATOR =>            "subreddits/mine/moderator/",
    MY_MULTIREDDITS =>         "api/multi/mine/",
    MY_SUBREDDITS =>           "subreddits/mine/subscriber/",
    POST_REQUIREMENTS =>       "api/v1/#subreddit/post_requirements/",
    PREFERENCES =>             "api/v1/me/prefs/",
    QUARANTINE_OPT_IN =>       "api/quarantine_optin/",
    QUARANTINE_OPT_OUT =>      "api/quarantine_optout/",
    READ_MESSAGE =>            "api/read_message/",
    REMOVAL_COMMENT_MESSAGE => "api/v1/modactions/removal_comment_message/",
    REMOVAL_LINK_MESSAGE =>    "api/v1/modactions/removal_link_message/",
    REMOVAL_REASONS =>         "api/v1/modactions/removal_reasons/",
    REMOVAL_REASON =>          "api/v1/#subreddit/removal_reasons/#id/",
    REMOVAL_REASONS_LIST =>    "api/v1/#subreddit/removal_reasons/",
    REMOVE_SUBREDDIT_RULE =>   "api/remove_subreddit_rule/",
    REMOVE =>                  "api/remove/",
    REORDER_SUBREDDIT_RULES => "api/reorder_subreddit_rules/",
    REPORT =>                  "api/report/",
    RULES =>                   "r/#subreddit/about/rules/",
    SAVE =>                    "api/save/",
    SEARCH =>                  "search/",
    SELECT_FLAIR =>            "r/#subreddit/api/selectflair/",
    SENDREPLIES =>             "api/sendreplies/",
    SENT =>                    "message/sent/",
    SET_ORIGINAL_CONTENT =>    "api/set_original_content/",
    SETPERMISSIONS =>          "r/#subreddit/api/setpermissions/",
    SHOW_COMMENT =>            "api/show_comment/",
    SITE_ADMIN =>              "api/site_admin/",
    SPOILER =>                 "api/spoiler/",
    STICKY_SUBMISSION =>       "api/set_subreddit_sticky/",
    STORE_VISITS =>            "api/store_visits/",
    STRUCTURED_STYLES =>       "api/v1/structured_styles/#subreddit/",
    STYLE_ASSET_LEASE =>       "api/v1/style_asset_upload_s3/#subreddit/",
    SUB_RECOMMENDED =>         "api/recommend/sr/#subreddits/",
    SUBMISSION =>              "comments/#id/",
    SUBMISSION_REPLIES =>      "message/selfreply/",
    SUBMIT =>                  "api/submit/",
    SUBMIT_POLL_POST =>        "api/submit_poll_post/",
    SUBREDDIT =>               "r/#subreddit/",
    SUBREDDIT_TOP =>           "r/#subreddit/top/",
    SUBREDDIT_RISING =>        "r/#subreddit/rising/",
    SUBREDDIT_NEW =>           "r/#subreddit/new/",
    SUBREDDIT_HOT =>           "r/#subreddit/hot/",
    SUBREDDIT_ABOUT =>         "r/#subreddit/about/",
    SUBREDDIT_FILTER =>        "api/filter/user/#user/f/#special/r/#subreddit/",
    SUBREDDIT_FILTER_LIST =>   "api/filter/user/#user/f/#special/",
    SUBREDDIT_RANDOM =>        "r/#subreddit/random/",
    SUBREDDIT_SETTINGS =>      "r/#subreddit/about/edit/",
    SUBREDDIT_STYLESHEET =>    "r/#subreddit/api/subreddit_stylesheet/",
    SUBREDDITS_BY_TOPIC =>     "api/subreddits_by_topic/",
    SUBREDDITS_DEFAULT =>      "subreddits/default/",
    SUBREDDITS_GOLD =>         "subreddits/gold/",
    SUBREDDITS_NAME_SEARCH =>  "api/search_reddit_names/",
    SUBREDDITS_NEW =>          "subreddits/new/",
    SUBREDDITS_POPULAR =>      "subreddits/popular/",
    SUBREDDIT_SEARCH =>         "r/#subreddit/search/",
    SUBREDDITS_SEARCH =>       "subreddits/search/",
    SUBSCRIBE =>               "api/subscribe/",
    SUGGESTED_SORT =>          "api/set_suggested_sort/",
    TROPHIES =>                "api/v1/user/#user/trophies/",
    UNCOLLAPSE =>              "api/uncollapse_message/",
    UNFRIEND =>                "r/#subreddit/api/unfriend/",
    UNHIDE =>                  "api/unhide/",
    UNIGNORE_REPORTS =>        "api/unignore_reports/",
    UNLOCK =>                  "api/unlock/",
    UNMARKNSFW =>              "api/unmarknsfw/",
    UNMUTE_SENDER =>           "api/unmute_message_author/",
    UNREAD =>                  "message/unread/",
    UNREAD_MESSAGE =>          "api/unread_message/",
    UNSAVE =>                  "api/unsave/",
    UNSPOILER =>               "api/unspoiler/",
    UPDATE_SETTINGS =>         "api/v1/subreddit/update_settings/",
    UPDATE_SUBREDDIT_RULE =>   "api/update_subreddit_rule/",
    UPLOAD_IMAGE =>            "r/#subreddit/api/upload_sr_img/",
    USER =>                    "user/#user/",
    USER_ABOUT =>              "user/#user/about/",
    USER_SUBMITTED =>          "user/#user/submitted/",
    USER_COMMENTS =>           "user/#user/comments/",
    USER_BY_FULLNAME =>        "/api/user_data_by_account_ids/",
    USER_FLAIR =>              "r/#subreddit/api/user_flair_v2",
    USERS_NEW =>               "users/new/",
    USERS_POPULAR =>           "users/popular/",
    USERS_SEARCH =>            "users/search/",
    VOTE =>                    "api/vote/",
    WIDGET_CREATE =>           "r/#subreddit/api/widget/",
    WIDGET_LEASE =>            "r/#subreddit/api/widget_image_upload_s3",
    WIDGET_MODIFY =>           "r/#subreddit/api/widget/{widget_id}",
    WIDGET_ORDER =>            "r/#subreddit/api/widget_order/#section/",
    WIDGETS =>                 "r/#subreddit/api/widgets/",
    WIKI_EDIT =>               "r/#subreddit/api/wiki/edit/",
    WIKI_PAGE =>               "r/#subreddit/wiki/#page/",
    WIKI_PAGE_EDITOR =>        "r/#subreddit/api/wiki/alloweditor/#method/",
    WIKI_PAGE_REVISIONS =>     "r/#subreddit/wiki/revisions/#page/",
    WIKI_PAGE_SETTINGS =>      "r/#subreddit/wiki/settings/#page/",
    WIKI_PAGES =>              "r/#subreddit/wiki/pages/",
    WIKI_REVISIONS =>          "r/#subreddit/wiki/revisions/"
}