egg_mode/list/mod.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
5//! Structs and functions for working with lists.
6//!
7//! A list is a way to group accounts together, either as a way to highlight those accounts, or to
8//! view as a subset of or supplement to your main timeline. A list can be viewed in the same way
9//! as the user's own timelines, loading in pages with the same [`Timeline`] interface.
10//!
11//! [`Timeline`]: ../tweet/struct.Timeline.html
12//!
13//! Lists can be private or public. If a list if public, then other users can view the list's
14//! members and the statuses of those members. (Statuses by protected accounts can still only be
15//! read by approved followers.)
16//!
17//! A list has "members", those accounts that are tracked as part of the list. When you call
18//! `statuses` on a given list, you're looking at the recent posts by its members. Members can be
19//! added and removed either one at a time, or in batches. Protected accounts can only be added to
20//! a list if the user is an approved follower of that account.
21//!
22//! A user can "subscribe" to another user's list as a sort of bookmark. Doing this places the list
23//! in the "lists" section of their profile, and in the `subscriptions` and `list` sets available
24//! from the API. Note that you don't need to subscribe to a list to view the statuses or users in
25//! it; you can do that to any public or personally owned list anyway. Subscribing merely allows
26//! quick access in case you want to keep track of a list someone else made.
27//!
28//! If a list is public, then all metadata about that list is public. Protected accounts can be
29//! seen as members of a list, but their statuses are still only visible to approved followers.
30//! None of the user-focused queries in this module assume that they're about the authenticated
31//! user; they can all be called in reference to any user.
32//!
33//! ## Types
34//!
35//! - `List`: This is the list metadata returned from Twitter when requesting information about the
36//! list itself, or when performing some modification to one.
37//! - `ListID`: There are two ways to reference a list in the Twitter API: Either via a unique
38//! numeric ID, or with its "slug" combined with a reference to the user who created it. This
39//! enum wraps that distinction into one type that all the methods take when they need to
40//! reference a list like this. See the enum's documentation for details on how to create one.
41//! - `ListUpdate`: When updating a list's metadata, all the fields that can be updated are
42//! optional, so the `update` function returns this builder struct so you don't have to provide
43//! all the parameters if you don't need to.
44//!
45//! ## Functions
46//!
47//! ### Basic actions
48//!
49//! These functions perform basic write actions on lists as a whole. These all require write access
50//! to the authenticated user's account.
51//!
52//! - `create`/`delete`
53//! - `update` (see `ListUpdate` for full details)
54//! - `subscribe`/`unsubscribe`
55//! - `add_member`/`remove_member`
56//! - `add_member_list`/`remove_member_list`
57//!
58//! ### Basic queries
59//!
60//! These functions let you query information about lists, or related to lists somehow.
61//!
62//! - `ownerships`/`subscriptions`/`list`: Note that `list` will only return the most recent 100
63//! lists in the `ownerships`/`subscriptions` sets.
64//! - `memberships`
65//! - `members`/`is_member`
66//! - `subscribers`/`is_subscriber`
67//! - `show`
68//! - `statuses`
69
70use chrono;
71use serde::Deserialize;
72
73use crate::common::*;
74use crate::{auth, links, user};
75
76mod fun;
77pub use self::fun::*;
78
79/// Convenience enum to refer to a list via its owner and name or via numeric ID.
80///
81/// Any API call that needs to reference a specific list has a set of parameters that collectively
82/// refer to it. Not only do lists have a unique numeric ID that refers to them, they have a "slug"
83/// that stands in as the list's unique name. This slug is only unique when taken in combination
84/// with the user that created it, though, so this leads to the raw API call having parameters that
85/// refer to the user by screen name or ID, or the list as a whole by this pair of slug parameters
86/// or the single ID parameter. egg-mode wraps this pattern with this `ListID` enum.
87///
88/// Because the slug is formed from two variables instead of one, this enum foregoes the many
89/// `From` implementations that `UserID` has and instead opts for two creation functions. If you
90/// have a user/name combo, use `ListID::from_slug` when looking for the list. If you have the
91/// list's ID instead, then you can use `ListID::from_id`.
92///
93/// # Example
94///
95/// ```rust
96/// use egg_mode::list::ListID;
97///
98/// //The following two ListIDs refer to the same list:
99/// let slug = ListID::from_slug("Twitter", "support");
100/// let id = ListID::from_id(99924643);
101/// ```
102#[derive(Debug, Clone)]
103pub enum ListID {
104 ///Referring via the list's owner and its "slug" or name.
105 Slug(user::UserID, CowStr),
106 ///Referring via the list's numeric ID.
107 ID(u64),
108}
109
110impl ListID {
111 ///Make a new `ListID` by supplying its owner and name.
112 pub fn from_slug<T: Into<user::UserID>>(owner: T, list_name: impl Into<CowStr>) -> ListID {
113 ListID::Slug(owner.into(), list_name.into())
114 }
115
116 ///Make a new `ListID` by supplying its numeric ID.
117 pub fn from_id(list_id: u64) -> ListID {
118 ListID::ID(list_id)
119 }
120}
121
122/// Represents the metadata for a list.
123///
124/// Because of the myriad ways to reference a list, there are a few seemingly-redundant fields on
125/// here. It's worthwhile to understand all the referential fields:
126///
127/// * `name` is the human-readable name of the list. Notably, this can contain spaces and uppercase
128/// letters.
129/// * `slug` is simply `name` converted to a format that can be put into a URL and used to
130/// reference the list for API calls.
131/// * `full_name` is how you'd link the list as a @mention, in the form `@screen_name/slug`.
132/// * `id` is the numeric ID, which can be used with `ListID::from_id` to make a `ListID` for the
133/// list.
134/// * `uri` is how you assemble a link to the list. Start with `"https://twitter.com"`, concat this
135/// field to the end, and you have a full URL. Note that the field does start with its own slash.
136/// * `user` is a mostly-populated `TwitterUser` corresponding to the creator of the list. If you
137/// combine `user.screen_name` or `user.id` with `slug`, you can send them to `ListID::from_slug`
138/// to make a `ListID` for the list.
139#[derive(Clone, Debug, Deserialize)]
140pub struct List {
141 ///The name of the list.
142 pub name: String,
143 ///The user who created the list.
144 pub user: user::TwitterUser,
145 ///The "slug" of a list, that can be combined with its creator's `UserID` to refer to the list.
146 pub slug: String,
147 ///The numeric ID of the list.
148 pub id: u64,
149 ///The number of accounts "subscribed" to the list, for whom it will appear in their collection
150 ///of available lists.
151 pub subscriber_count: u64,
152 ///The number of accounts added to the list.
153 pub member_count: u64,
154 ///The full name of the list, preceded by `@`, that can be used to link to the list as part of
155 ///a tweet, direct message, or other place on Twitter where @mentions are parsed.
156 pub full_name: String,
157 ///The description of the list, as entered by its creator.
158 pub description: String,
159 ///The full name of the list, preceded by `/`, that can be preceded with `https://twitter.com`
160 ///to create a link to the list.
161 pub uri: String,
162 ///UTC timestamp of when the list was created.
163 #[serde(with = "serde_datetime")]
164 pub created_at: chrono::DateTime<chrono::Utc>,
165}
166
167/// Represents a pending update to a list's metadata.
168///
169/// As updating a list could modify each field independently, this operation is exposed as a builder
170/// struct. To update any field, call the method named after that field, then call `send` to send
171/// the update to Twitter.
172///
173/// # Example
174///
175/// ```rust,no_run
176/// # use egg_mode::Token;
177/// # #[tokio::main]
178/// # async fn main() {
179/// # let token: Token = unimplemented!();
180/// use egg_mode::list::{self, ListID};
181///
182/// //remember, you can only update a list if you own it!
183/// let update = list::update(ListID::from_slug("Twitter", "support"));
184/// let list = update.name("Official Support").send(&token).await.unwrap();
185/// # }
186/// ```
187pub struct ListUpdate {
188 list: ListID,
189 name: Option<String>,
190 public: Option<bool>,
191 desc: Option<String>,
192}
193
194impl ListUpdate {
195 ///Updates the name of the list.
196 pub fn name(self, name: impl Into<String>) -> ListUpdate {
197 ListUpdate {
198 name: Some(name.into()),
199 ..self
200 }
201 }
202
203 ///Sets whether the list is public.
204 pub fn public(self, public: bool) -> ListUpdate {
205 ListUpdate {
206 public: Some(public),
207 ..self
208 }
209 }
210
211 ///Updates the description of the list.
212 pub fn desc(self, desc: String) -> ListUpdate {
213 ListUpdate {
214 desc: Some(desc),
215 ..self
216 }
217 }
218
219 ///Sends the update request to Twitter.
220 pub async fn send(self, token: &auth::Token) -> Result<Response<List>, crate::error::Error> {
221 let params = ParamList::new()
222 .add_list_param(self.list)
223 .add_opt_param("name", self.name)
224 .add_opt_param(
225 "mode",
226 self.public.map(|p| if p { "public" } else { "private" }),
227 )
228 .add_opt_param("description", self.desc);
229
230 let req = post(links::lists::UPDATE, token, Some(¶ms));
231 request_with_json_response(req).await
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::List;
238 use crate::common::tests::load_file;
239
240 #[test]
241 fn parse_list_sample() {
242 let content = load_file("sample_payloads/sample-list.json");
243 let list = ::serde_json::from_str::<List>(&content).unwrap();
244 assert_eq!(list.full_name, "@Scobleizer/all-people-in-spatial-2");
245 assert_eq!(list.user.screen_name, "Scobleizer")
246 }
247}