egg_mode/list/
fun.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use std::convert::TryFrom;
6
7use super::*;
8
9use crate::common::RateLimit;
10use crate::cursor::{CursorIter, ListCursor, UserCursor};
11use crate::error::{Error::TwitterError, Result};
12use crate::user::{TwitterUser, UserID};
13use crate::{auth, links, tweet};
14
15///Look up the lists the given user has been added to.
16///
17///This function returns a `Stream` over the lists returned by Twitter. This method defaults to
18///reeturning 20 lists in a single network call; the maximum is 1000.
19pub fn memberships<T: Into<UserID>>(user: T, token: &auth::Token) -> CursorIter<ListCursor> {
20    let params = ParamList::new().add_user_param(user.into());
21    CursorIter::new(links::lists::MEMBERSHIPS, token, Some(params), Some(20))
22}
23
24///Return up to 100 lists the given user is subscribed to, including those the user made
25///themselves.
26///
27///This function can be used to get a snapshot of a user's lists, but if they've created or
28///subscribed to a lot of lists, then the limitations of this function can get in the way.
29///If the `owned_first` parameter is `true`, Twitter will load the lists the given user created,
30///then the ones they've subscribed to, stopping when it reaches 100 lists. If it's `false`, then
31///the lists are loaded in the opposite order.
32///
33///If the user has more than 100 lists total like this, you'll need to call `ownerships` and
34///`subscriptions` separately to be able to properly load everything.
35pub async fn list<'id, T: Into<UserID>>(
36    user: T,
37    owned_first: bool,
38    token: &auth::Token,
39) -> Result<Response<Vec<List>>> {
40    let params = ParamList::new()
41        .add_user_param(user.into())
42        .add_param("reverse", owned_first.to_string());
43
44    let req = get(links::lists::LIST, token, Some(&params));
45
46    request_with_json_response(req).await
47}
48
49///Look up the lists the given user is subscribed to, but not ones the user made themselves.
50///
51///This function returns a `Stream` over the lists returned by Twitter. This method defaults to
52///reeturning 20 lists in a single network call; the maximum is 1000.
53pub fn subscriptions<T: Into<UserID>>(user: T, token: &auth::Token) -> CursorIter<ListCursor> {
54    let params = ParamList::new().add_user_param(user.into());
55    CursorIter::new(links::lists::SUBSCRIPTIONS, token, Some(params), Some(20))
56}
57
58///Look up the lists created by the given user.
59///
60///This function returns a `Stream` over the lists returned by Twitter. This method defaults to
61///reeturning 20 lists in a single network call; the maximum is 1000.
62pub fn ownerships<T: Into<UserID>>(user: T, token: &auth::Token) -> CursorIter<ListCursor> {
63    let params = ParamList::new().add_user_param(user.into());
64    CursorIter::new(links::lists::OWNERSHIPS, token, Some(params), Some(20))
65}
66
67///Look up information for a single list.
68pub async fn show(list: ListID, token: &auth::Token) -> Result<Response<List>> {
69    let params = ParamList::new().add_list_param(list);
70
71    let req = get(links::lists::SHOW, token, Some(&params));
72
73    request_with_json_response(req).await
74}
75
76///Look up the users that have been added to the given list.
77///
78///This function returns a `Stream` over the users returned by Twitter. This method defaults to
79///reeturning 20 users in a single network call; the maximum is 5000.
80pub fn members(list: ListID, token: &auth::Token) -> CursorIter<UserCursor> {
81    let params = ParamList::new().add_list_param(list);
82
83    CursorIter::new(links::lists::MEMBERS, token, Some(params), Some(20))
84}
85
86///Look up the users that have subscribed to the given list.
87///
88///This function returns a `Stream` over the users returned by Twitter. This method defaults to
89///reeturning 20 users in a single network call; the maximum is 5000.
90pub fn subscribers(list: ListID, token: &auth::Token) -> CursorIter<UserCursor> {
91    let params = ParamList::new().add_list_param(list);
92
93    CursorIter::new(links::lists::SUBSCRIBERS, token, Some(params), Some(20))
94}
95
96///Check whether the given user is subscribed to the given list.
97pub async fn is_subscribed<'id, T: Into<UserID>>(
98    user: T,
99    list: ListID,
100    token: &auth::Token,
101) -> Result<Response<bool>> {
102    let params = ParamList::new()
103        .add_list_param(list)
104        .add_user_param(user.into());
105
106    let req = get(links::lists::IS_SUBSCRIBER, token, Some(&params));
107
108    let out = request_with_json_response::<TwitterUser>(req).await;
109
110    match out {
111        Ok(user) => Ok(Response::map(user, |_| true)),
112        Err(TwitterError(headers, terrs)) => {
113            if terrs.errors.iter().any(|e| e.code == 109) {
114                // here's a fun conundrum: since "is not in this list" is returned as an error code,
115                // the rate limit info that would otherwise be part of the response isn't there. the
116                // rate_headers method was factored out specifically for this location, since it's
117                // still there, just accompanying an error response instead of a user.
118                Ok(Response::new(RateLimit::try_from(&headers)?, false))
119            } else {
120                Err(TwitterError(headers, terrs))
121            }
122        }
123        Err(err) => Err(err),
124    }
125}
126
127///Check whether the given user has been added to the given list.
128pub async fn is_member<'id, T: Into<UserID>>(
129    user: T,
130    list: ListID,
131    token: &auth::Token,
132) -> Result<Response<bool>> {
133    let params = ParamList::new()
134        .add_list_param(list)
135        .add_user_param(user.into());
136
137    let req = get(links::lists::IS_MEMBER, token, Some(&params));
138    let out = request_with_json_response::<TwitterUser>(req).await;
139
140    match out {
141        Ok(resp) => Ok(Response::map(resp, |_| true)),
142        Err(TwitterError(headers, errors)) => {
143            if errors.errors.iter().any(|e| e.code == 109) {
144                // here's a fun conundrum: since "is not in this list" is returned as an error code,
145                // the rate limit info that would otherwise be part of the response isn't there. the
146                // rate_headers method was factored out specifically for this location, since it's
147                // still there, just accompanying an error response instead of a user.
148                Ok(Response::new(RateLimit::try_from(&headers)?, false))
149            } else {
150                Err(TwitterError(headers, errors))
151            }
152        }
153        Err(err) => Err(err),
154    }
155}
156
157///Begin navigating the collection of tweets made by the users added to the given list.
158///
159///The interface for loading statuses from a list is exactly the same as loading from a personal
160///timeline. see the [`Timeline`] docs for details.
161///
162///[`Timeline`]: ../tweet/struct.Timeline.html
163pub fn statuses(list: ListID, with_rts: bool, token: &auth::Token) -> tweet::Timeline {
164    let params = ParamList::new()
165        .add_list_param(list)
166        .add_param("include_rts", with_rts.to_string());
167
168    tweet::Timeline::new(links::lists::STATUSES, Some(params), token)
169}
170
171///Adds the given user to the given list.
172///
173///Note that lists cannot have more than 5000 members.
174///
175///Upon success, the future returned by this function yields the freshly-modified list.
176pub async fn add_member<'id, T: Into<UserID>>(
177    list: ListID,
178    user: T,
179    token: &auth::Token,
180) -> Result<Response<List>> {
181    let params = ParamList::new()
182        .add_list_param(list)
183        .add_user_param(user.into());
184
185    let req = post(links::lists::ADD, token, Some(&params));
186
187    request_with_json_response(req).await
188}
189
190///Adds a set of users to the given list.
191///
192///The `members` param can be used the same way as the `accts` param in [`user::lookup`]. See that
193///method's documentation for details.
194///
195///[`user::lookup`]: ../user/fn.lookup.html
196///
197///Note that you cannot add more than 100 members to a list at a time, and that lists in general
198///cannot have more than 5000 members.
199///
200///When using this method, take care not to add and remove many members in rapid succession; there
201///are no guarantees that the result of a `add_member_list` or `remove_member_list` will be
202///immediately available for a corresponding removal or addition, respectively.
203pub async fn add_member_list<'id, T, I>(
204    members: I,
205    list: ListID,
206    token: &auth::Token,
207) -> Result<Response<List>>
208where
209    T: Into<UserID>,
210    I: IntoIterator<Item = T>,
211{
212    let (id_param, name_param) = multiple_names_param(members);
213    let params = ParamList::new()
214        .add_list_param(list)
215        .add_opt_param(
216            "user_id",
217            if !id_param.is_empty() {
218                Some(id_param)
219            } else {
220                None
221            },
222        )
223        .add_opt_param(
224            "screen_name",
225            if !name_param.is_empty() {
226                Some(name_param)
227            } else {
228                None
229            },
230        );
231
232    let req = post(links::lists::ADD_LIST, token, Some(&params));
233
234    request_with_json_response(req).await
235}
236
237///Removes the given user from the given list.
238pub async fn remove_member<'id, T: Into<UserID>>(
239    list: ListID,
240    user: T,
241    token: &auth::Token,
242) -> Result<Response<List>> {
243    let params = ParamList::new()
244        .add_list_param(list)
245        .add_user_param(user.into());
246
247    let req = post(links::lists::REMOVE_MEMBER, token, Some(&params));
248
249    request_with_json_response(req).await
250}
251
252///Removes a set of users from the given list.
253///
254///The `members` param can be used the same way as the `accts` param in [`user::lookup`]. See that
255///method's documentation for details.
256///
257///[`user::lookup`]: ../user/fn.lookup.html
258///
259///This method is limited to removing 100 members at a time.
260///
261///When using this method, take care not to add and remove many members in rapid succession; there
262///are no guarantees that the result of a `add_member_list` or `remove_member_list` will be
263///immediately available for a corresponding removal or addition, respectively.
264pub async fn remove_member_list<T, I>(
265    members: I,
266    list: ListID,
267    token: &auth::Token,
268) -> Result<Response<List>>
269where
270    T: Into<UserID>,
271    I: IntoIterator<Item = T>,
272{
273    let (id_param, name_param) = multiple_names_param(members);
274    let params = ParamList::new()
275        .add_list_param(list)
276        .add_opt_param(
277            "user_id",
278            if !id_param.is_empty() {
279                Some(id_param)
280            } else {
281                None
282            },
283        )
284        .add_opt_param(
285            "screen_name",
286            if !name_param.is_empty() {
287                Some(name_param)
288            } else {
289                None
290            },
291        );
292
293    let req = post(links::lists::REMOVE_LIST, token, Some(&params));
294
295    request_with_json_response(req).await
296}
297
298///Creates a list, with the given name, visibility, and description.
299///
300///The new list is owned by the authenticated user, and its slug can be created with their handle
301///and the name given to `name`. Twitter places an upper limit on 1000 lists owned by a single
302///account.
303pub async fn create(
304    name: String,
305    public: bool,
306    desc: Option<String>,
307    token: &auth::Token,
308) -> Result<Response<List>> {
309    let params = ParamList::new()
310        .add_param("name", name)
311        .add_param("mode", if public { "public" } else { "private" })
312        .add_opt_param("description", desc);
313
314    let req = post(links::lists::CREATE, token, Some(&params));
315
316    request_with_json_response(req).await
317}
318
319///Deletes the given list.
320///
321///The authenticated user must have created the list.
322pub async fn delete(list: ListID, token: &auth::Token) -> Result<Response<List>> {
323    let params = ParamList::new().add_list_param(list);
324
325    let req = post(links::lists::DELETE, token, Some(&params));
326
327    request_with_json_response(req).await
328}
329
330///Subscribes the authenticated user to the given list.
331///
332///Subscribing to a list is a way to make it available in the "Lists" section of a user's profile
333///without having to create it themselves.
334pub async fn subscribe(list: ListID, token: &auth::Token) -> Result<Response<List>> {
335    let params = ParamList::new().add_list_param(list);
336
337    let req = post(links::lists::SUBSCRIBE, token, Some(&params));
338
339    request_with_json_response(req).await
340}
341
342///Unsubscribes the authenticated user from the given list.
343pub async fn unsubscribe(list: ListID, token: &auth::Token) -> Result<Response<List>> {
344    let params = ParamList::new().add_list_param(list);
345
346    let req = post(links::lists::UNSUBSCRIBE, token, Some(&params));
347
348    request_with_json_response(req).await
349}
350
351///Begins updating a list's metadata.
352///
353///This method is exposed using a builder struct. See the [`ListUpdate`] docs for details.
354///
355///[`ListUpdate`]: struct.ListUpdate.html
356pub fn update(list: ListID) -> ListUpdate {
357    ListUpdate {
358        list,
359        name: None,
360        public: None,
361        desc: None,
362    }
363}