1#![warn(bad_style)]
2#![warn(unused)]
4#![warn(unused_extern_crates)]
5#![warn(unused_import_braces)]
6#![warn(unused_qualifications)]
7#![warn(unused_results)]
8
9use oauth::RequestBuilder;
10use oauth_client::Token;
11use serde::{Deserialize, Serialize};
12use std::{borrow::Cow, collections::HashMap, str};
13use thiserror::Error;
14
15pub use oauth_client as oauth;
16pub use serde_json;
17
18pub type Result<T> = std::result::Result<T, Error>;
19
20#[derive(Debug, Error)]
21#[non_exhaustive]
22pub enum Error {
23 #[error("OAuth error: {0}")]
24 Oauth(#[from] oauth::Error),
25 #[error("JSON error: {0}")]
26 Json(#[from] serde_json::Error),
27 #[error("decode string error: {0}")]
28 FromUtf8(#[from] str::Utf8Error),
29}
30
31mod api_twitter_oauth {
32 pub const REQUEST_TOKEN: &str = "https://api.twitter.com/oauth/request_token";
33 pub const AUTHORIZE: &str = "https://api.twitter.com/oauth/authorize";
34 pub const ACCESS_TOKEN: &str = "https://api.twitter.com/oauth/access_token";
35}
36
37mod api_twitter_soft {
38 pub const UPDATE_STATUS: &str = "https://api.twitter.com/1.1/statuses/update.json";
39 pub const HOME_TIMELINE: &str = "https://api.twitter.com/1.1/statuses/home_timeline.json";
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
43pub struct Tweet {
44 pub created_at: String,
45 pub text: String,
46}
47
48impl Tweet {
49 pub fn parse_timeline(json_str: impl AsRef<str>) -> Result<Vec<Tweet>> {
50 let tweets = serde_json::from_str(json_str.as_ref())?;
51 Ok(tweets)
52 }
53}
54
55fn split_query(query: &str) -> HashMap<Cow<'_, str>, Cow<'_, str>> {
56 let mut param = HashMap::new();
57 for q in query.split('&') {
58 let (k, v) = q.split_once('=').unwrap();
59 let _ = param.insert(k.into(), v.into());
60 }
61 param
62}
63
64pub fn get_request_token<RB>(
65 consumer: &Token<'_>,
66 client: &RB::ClientBuilder,
67) -> Result<Token<'static>>
68where
69 RB: RequestBuilder,
70 RB::ReturnValue: AsRef<[u8]>,
71{
72 let bytes: RB::ReturnValue = oauth::get::<RB>(
73 api_twitter_oauth::REQUEST_TOKEN,
74 consumer,
75 None,
76 None,
77 client,
78 )?;
79 let resp = str::from_utf8(bytes.as_ref())?;
80 let param = split_query(resp);
81 let token = Token::new(
82 param.get("oauth_token").unwrap().to_string(),
83 param.get("oauth_token_secret").unwrap().to_string(),
84 );
85 Ok(token)
86}
87
88pub fn get_authorize_url(request: &Token<'_>) -> String {
89 format!(
90 "{}?oauth_token={}",
91 api_twitter_oauth::AUTHORIZE,
92 request.key
93 )
94}
95
96pub fn get_access_token<RB>(
97 consumer: &Token<'_>,
98 request: &Token<'_>,
99 pin: &str,
100 client: &RB::ClientBuilder,
101) -> Result<Token<'static>>
102where
103 RB: RequestBuilder,
104 RB::ReturnValue: AsRef<[u8]>,
105{
106 let mut param = HashMap::new();
107 let _ = param.insert("oauth_verifier".into(), pin.into());
108 let bytes = oauth::get::<RB>(
109 api_twitter_oauth::ACCESS_TOKEN,
110 consumer,
111 Some(request),
112 Some(¶m),
113 client,
114 )?;
115 let resp = str::from_utf8(bytes.as_ref())?;
116 let param = split_query(resp);
117 let token = Token::new(
118 param.get("oauth_token").unwrap().to_string(),
119 param.get("oauth_token_secret").unwrap().to_string(),
120 );
121 Ok(token)
122}
123
124pub fn update_status<RB>(
127 consumer: &Token<'_>,
128 access: &Token<'_>,
129 status: &str,
130 client: &RB::ClientBuilder,
131) -> Result<()>
132where
133 RB: RequestBuilder,
134 RB::ReturnValue: AsRef<[u8]>,
135{
136 let mut param = HashMap::new();
137 let _ = param.insert("status".into(), status.into());
138 let _ = oauth::post::<RB>(
139 api_twitter_soft::UPDATE_STATUS,
140 consumer,
141 Some(access),
142 Some(¶m),
143 client,
144 )?;
145 Ok(())
146}
147
148pub fn get_last_tweets<RB>(
149 consumer: &Token<'_>,
150 access: &Token<'_>,
151 client: &RB::ClientBuilder,
152) -> Result<Vec<Tweet>>
153where
154 RB: RequestBuilder,
155 RB::ReturnValue: AsRef<[u8]>,
156{
157 let bytes = oauth::get::<RB>(
158 api_twitter_soft::HOME_TIMELINE,
159 consumer,
160 Some(access),
161 None,
162 client,
163 )?;
164 let last_tweets_json = str::from_utf8(bytes.as_ref())?;
165 let ts = Tweet::parse_timeline(&last_tweets_json)?;
166 Ok(ts)
167}