@@ -15,6 +15,8 @@ const REDDIT_ANDROID_OAUTH_CLIENT_ID: &str = "ohXpoqrZYub1kg";
15
15
16
16
const AUTH_ENDPOINT : & str = "https://www.reddit.com" ;
17
17
18
+ const OAUTH_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
19
+
18
20
// Spoofed client for Android devices
19
21
#[ derive( Debug , Clone , Default ) ]
20
22
pub struct Oauth {
@@ -32,24 +34,30 @@ impl Oauth {
32
34
loop {
33
35
let attempt = Self :: new_with_timeout ( ) . await ;
34
36
match attempt {
35
- Ok ( Some ( oauth) ) => {
37
+ Ok ( Ok ( oauth) ) => {
36
38
info ! ( "[✅] Successfully created OAuth client" ) ;
37
39
return oauth;
38
40
}
39
- Ok ( None ) => {
40
- error ! ( "Failed to create OAuth client. Retrying in 5 seconds..." ) ;
41
+ Ok ( Err ( e) ) => {
42
+ error ! ( "Failed to create OAuth client: {}. Retrying in 5 seconds..." , {
43
+ match e {
44
+ AuthError :: Hyper ( error) => error. to_string( ) ,
45
+ AuthError :: SerdeDeserialize ( error) => error. to_string( ) ,
46
+ AuthError :: Field ( ( value, error) ) => format!( "{error}\n {value}" ) ,
47
+ }
48
+ } ) ;
41
49
}
42
- Err ( duration ) => {
43
- error ! ( "Failed to create OAuth client in {duration:?} . Retrying in 5 seconds..." ) ;
50
+ Err ( _ ) => {
51
+ error ! ( "Failed to create OAuth client before timeout . Retrying in 5 seconds..." ) ;
44
52
}
45
53
}
46
- tokio:: time:: sleep ( Duration :: from_secs ( 5 ) ) . await ;
54
+ tokio:: time:: sleep ( OAUTH_TIMEOUT ) . await ;
47
55
}
48
56
}
49
57
50
- async fn new_with_timeout ( ) -> Result < Option < Self > , Elapsed > {
58
+ async fn new_with_timeout ( ) -> Result < Result < Self , AuthError > , Elapsed > {
51
59
let mut oauth = Self :: default ( ) ;
52
- timeout ( Duration :: from_secs ( 5 ) , oauth. login ( ) ) . await . map ( |result| result. map ( |_| oauth) )
60
+ timeout ( OAUTH_TIMEOUT , oauth. login ( ) ) . await . map ( |result : Result < ( ) , AuthError > | result. map ( |_| oauth) )
53
61
}
54
62
55
63
pub ( crate ) fn default ( ) -> Self {
@@ -66,7 +74,7 @@ impl Oauth {
66
74
device,
67
75
}
68
76
}
69
- async fn login ( & mut self ) -> Option < ( ) > {
77
+ async fn login ( & mut self ) -> Result < ( ) , AuthError > {
70
78
// Construct URL for OAuth token
71
79
let url = format ! ( "{AUTH_ENDPOINT}/auth/v2/oauth/access-token/loid" ) ;
72
80
let mut builder = Request :: builder ( ) . method ( Method :: POST ) . uri ( & url) ;
@@ -95,7 +103,7 @@ impl Oauth {
95
103
96
104
// Send request
97
105
let client: & once_cell:: sync:: Lazy < client:: Client < _ , Body > > = & CLIENT ;
98
- let resp = client. request ( request) . await . ok ( ) ?;
106
+ let resp = client. request ( request) . await ?;
99
107
100
108
trace ! ( "Received response with status {} and length {:?}" , resp. status( ) , resp. headers( ) . get( "content-length" ) ) ;
101
109
trace ! ( "OAuth headers: {:#?}" , resp. headers( ) ) ;
@@ -106,30 +114,58 @@ impl Oauth {
106
114
// Not worried about the privacy implications, since this is randomly changed
107
115
// and really only as privacy-concerning as the OAuth token itself.
108
116
if let Some ( header) = resp. headers ( ) . get ( "x-reddit-loid" ) {
109
- self . headers_map . insert ( "x-reddit-loid" . to_owned ( ) , header. to_str ( ) . ok ( ) ? . to_string ( ) ) ;
117
+ self . headers_map . insert ( "x-reddit-loid" . to_owned ( ) , header. to_str ( ) . unwrap ( ) . to_string ( ) ) ;
110
118
}
111
119
112
120
// Same with x-reddit-session
113
121
if let Some ( header) = resp. headers ( ) . get ( "x-reddit-session" ) {
114
- self . headers_map . insert ( "x-reddit-session" . to_owned ( ) , header. to_str ( ) . ok ( ) ? . to_string ( ) ) ;
122
+ self . headers_map . insert ( "x-reddit-session" . to_owned ( ) , header. to_str ( ) . unwrap ( ) . to_string ( ) ) ;
115
123
}
116
124
117
125
trace ! ( "Serializing response..." ) ;
118
126
119
127
// Serialize response
120
- let body_bytes = hyper:: body:: to_bytes ( resp. into_body ( ) ) . await . ok ( ) ?;
121
- let json: serde_json:: Value = serde_json:: from_slice ( & body_bytes) . ok ( ) ?;
128
+ let body_bytes = hyper:: body:: to_bytes ( resp. into_body ( ) ) . await ?;
129
+ let json: serde_json:: Value = serde_json:: from_slice ( & body_bytes) ?;
122
130
123
131
trace ! ( "Accessing relevant fields..." ) ;
124
132
125
133
// Save token and expiry
126
- self . token = json. get ( "access_token" ) ?. as_str ( ) ?. to_string ( ) ;
127
- self . expires_in = json. get ( "expires_in" ) ?. as_u64 ( ) ?;
134
+ self . token = json
135
+ . get ( "access_token" )
136
+ . ok_or_else ( || AuthError :: Field ( ( json. clone ( ) , "access_token" ) ) ) ?
137
+ . as_str ( )
138
+ . ok_or_else ( || AuthError :: Field ( ( json. clone ( ) , "access_token: as_str" ) ) ) ?
139
+ . to_string ( ) ;
140
+ self . expires_in = json
141
+ . get ( "expires_in" )
142
+ . ok_or_else ( || AuthError :: Field ( ( json. clone ( ) , "expires_in" ) ) ) ?
143
+ . as_u64 ( )
144
+ . ok_or_else ( || AuthError :: Field ( ( json. clone ( ) , "expires_in: as_u64" ) ) ) ?;
128
145
self . headers_map . insert ( "Authorization" . to_owned ( ) , format ! ( "Bearer {}" , self . token) ) ;
129
146
130
147
info ! ( "[✅] Success - Retrieved token \" {}...\" , expires in {}" , & self . token[ ..32 ] , self . expires_in) ;
131
148
132
- Some ( ( ) )
149
+ Ok ( ( ) )
150
+ }
151
+ }
152
+
153
+ #[ derive( Debug ) ]
154
+ enum AuthError {
155
+ Hyper ( hyper:: Error ) ,
156
+ SerdeDeserialize ( serde_json:: Error ) ,
157
+ Field ( ( serde_json:: Value , & ' static str ) ) ,
158
+ }
159
+
160
+ impl From < hyper:: Error > for AuthError {
161
+ fn from ( err : hyper:: Error ) -> Self {
162
+ AuthError :: Hyper ( err)
163
+ }
164
+ }
165
+
166
+ impl From < serde_json:: Error > for AuthError {
167
+ fn from ( err : serde_json:: Error ) -> Self {
168
+ AuthError :: SerdeDeserialize ( err)
133
169
}
134
170
}
135
171
0 commit comments