twapi_v2/api/
post_2_media_upload_init.rs

1use crate::responses::errors::Errors;
2use crate::responses::media_upload::MediaUpload;
3use crate::{
4    api::{apply_options, execute_twitter, make_url, Authentication, TwapiOptions},
5    error::Error,
6    headers::Headers,
7};
8use reqwest::multipart::Form;
9use reqwest::RequestBuilder;
10use serde::{Deserialize, Serialize};
11
12const URL: &str = "/2/media/upload";
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
15#[serde(rename_all = "snake_case")]
16pub enum MediaCategory {
17    AmplifyVideo,
18    TweetGif,
19    TweetImage,
20    TweetVideo,
21    DmVideo,
22    DmImage,
23    Subtitles,
24    DmGif,
25}
26
27impl std::fmt::Display for MediaCategory {
28    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29        let value = match self {
30            Self::AmplifyVideo => "amplify_video",
31            Self::TweetGif => "tweet_gif",
32            Self::TweetImage => "tweet_image",
33            Self::TweetVideo => "tweet_video",
34            Self::DmVideo => "dm_video",
35            Self::DmImage => "dm_image",
36            Self::Subtitles => "subtitles",
37            Self::DmGif => "dm_gif",
38        };
39        write!(f, "{}", value)
40    }
41}
42
43#[derive(Debug, Clone, Default)]
44pub struct FormData {
45    pub total_bytes: u64,
46    pub media_type: String,
47    pub media_category: Option<MediaCategory>,
48    pub additional_owners: Option<String>,
49}
50
51impl FormData {
52    fn make_form(self) -> Form {
53        let mut res = Form::new()
54            .text("command", "INIT")
55            .text("total_bytes", self.total_bytes.to_string())
56            .text("media_type", self.media_type);
57        if let Some(media_category) = self.media_category {
58            res = res.text("media_category", media_category.to_string());
59        }
60        if let Some(additional_owners) = self.additional_owners {
61            res = res.text("additional_owners", additional_owners);
62        }
63        res
64    }
65}
66
67#[derive(Debug, Clone, Default)]
68pub struct Api {
69    form: FormData,
70    twapi_options: Option<TwapiOptions>,
71}
72
73impl Api {
74    pub fn new(form: FormData) -> Self {
75        Self {
76            form,
77            ..Default::default()
78        }
79    }
80
81    pub fn twapi_options(mut self, value: TwapiOptions) -> Self {
82        self.twapi_options = Some(value);
83        self
84    }
85
86    pub fn build(self, authentication: &impl Authentication) -> RequestBuilder {
87        let client = reqwest::Client::new();
88        let url = make_url(&self.twapi_options, URL);
89        let builder = client.post(&url).multipart(self.form.make_form());
90        authentication.execute(
91            apply_options(builder, &self.twapi_options),
92            "POST",
93            &url,
94            &[],
95        )
96    }
97
98    pub async fn execute(
99        self,
100        authentication: &impl Authentication,
101    ) -> Result<(Response, Headers), Error> {
102        execute_twitter(self.build(authentication)).await
103    }
104}
105
106#[derive(Serialize, Deserialize, Debug, Clone, Default)]
107pub struct Response {
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub data: Option<MediaUpload>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub errors: Option<Vec<Errors>>,
112    #[serde(flatten)]
113    pub extra: std::collections::HashMap<String, serde_json::Value>,
114}
115
116impl Response {
117    pub fn is_empty_extra(&self) -> bool {
118        let res = self.extra.is_empty()
119            && self
120                .data
121                .as_ref()
122                .map(|it| it.is_empty_extra())
123                .unwrap_or(true)
124            && self
125                .errors
126                .as_ref()
127                .map(|it| it.iter().all(|item| item.is_empty_extra()))
128                .unwrap_or(true);
129        if !res {
130            println!("Response {:?}", self.extra);
131        }
132        res
133    }
134}