twapi_v2/api/
post_2_media_upload_init.rs1use 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}