1use std::{
5 marker::PhantomData,
6 time::SystemTime,
7};
8use hmac::{Hmac, Mac};
9use sha2::Sha256;
10use rand::{Rng, distributions::Alphanumeric};
11use serde::{de::DeserializeOwned, Deserialize, Serialize};
12use serde_json::json;
13use crypto_botters_api::{HandlerOption, HandlerOptions, HttpOption, WebSocketOption};
14use generic_api_client::{http::{*, header::HeaderValue}, websocket::*};
15
16pub type BitFlyerRequestResult<T> = Result<T, BitFlyerRequestError>;
18pub type BitFlyerRequestError = RequestError<&'static str, BitFlyerHandlerError>;
19
20pub enum BitFlyerOption {
22 Default,
24 Key(String),
26 Secret(String),
28 HttpUrl(BitFlyerHttpUrl),
30 HttpAuth(bool),
32 RequestConfig(RequestConfig),
35 WebSocketUrl(BitFlyerWebSocketUrl),
37 WebSocketAuth(bool),
39 WebSocketChannels(Vec<String>),
41 WebSocketConfig(WebSocketConfig),
45}
46
47#[derive(Clone, Debug)]
49pub struct BitFlyerOptions {
50 pub key: Option<String>,
52 pub secret: Option<String>,
54 pub http_url: BitFlyerHttpUrl,
56 pub http_auth: bool,
58 pub request_config: RequestConfig,
60 pub websocket_url: BitFlyerWebSocketUrl,
62 pub websocket_auth: bool,
64 pub websocket_channels: Vec<String>,
66 pub websocket_config: WebSocketConfig,
68}
69
70#[derive(Debug, Eq, PartialEq, Copy, Clone)]
72pub enum BitFlyerHttpUrl {
73 Default,
75 None,
77}
78
79#[derive(Debug, Eq, PartialEq, Copy, Clone)]
81#[non_exhaustive]
82pub enum BitFlyerWebSocketUrl {
83 Default,
85 None,
87}
88
89#[derive(Deserialize, Debug)]
90pub struct BitFlyerChannelMessage {
91 pub channel: String,
92 pub message: serde_json::Value,
93}
94
95#[derive(Debug)]
96pub enum BitFlyerHandlerError {
97 ApiError(serde_json::Value),
98 ParseError,
99}
100
101pub struct BitFlyerRequestHandler<'a, R: DeserializeOwned> {
103 options: BitFlyerOptions,
104 _phantom: PhantomData<&'a R>,
105}
106
107pub struct BitFlyerWebSocketHandler<H: FnMut(BitFlyerChannelMessage) + Send + 'static> {
109 message_handler: H,
110 auth_id: Option<String>,
111 options: BitFlyerOptions,
112}
113
114impl<'a, B, R> RequestHandler<B> for BitFlyerRequestHandler<'a, R>
115where
116 B: Serialize,
117 R: DeserializeOwned,
118{
119 type Successful = R;
120 type Unsuccessful = BitFlyerHandlerError;
121 type BuildError = &'static str;
122
123 fn request_config(&self) -> RequestConfig {
124 let mut config = self.options.request_config.clone();
125 if self.options.http_url != BitFlyerHttpUrl::None {
126 config.url_prefix = self.options.http_url.as_str().to_owned();
127 }
128 config
129 }
130
131 fn build_request(&self, mut builder: RequestBuilder, request_body: &Option<B>, _: u8) -> Result<Request, Self::BuildError> {
132 if let Some(body) = request_body {
133 let json = serde_json::to_vec(body).or(Err("could not serialize body as application/json"))?;
134 builder = builder
135 .header(header::CONTENT_TYPE, "application/json")
136 .body(json);
137 }
138
139 let mut request = builder.build().or(Err("failed to build request"))?;
140
141 if self.options.http_auth {
142 let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = time.as_millis() as u64;
145
146 let mut path = request.url().path().to_owned();
147 if let Some(query) = request.url().query() {
148 path.push('?');
149 path.push_str(query)
150 }
151 let body = request.body()
152 .and_then(|body| body.as_bytes())
153 .map(String::from_utf8_lossy)
154 .unwrap_or_default();
155
156 let sign_contents = format!("{}{}{}{}", timestamp, request.method(), path, body);
157
158 let secret = self.options.secret.as_deref().ok_or("API secret not set")?;
159 let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); hmac.update(sign_contents.as_bytes());
162 let signature = hex::encode(hmac.finalize().into_bytes());
163
164 let key = HeaderValue::from_str(self.options.key.as_deref().ok_or("API key not set")?).or(
165 Err("invalid character in API key")
166 )?;
167 let headers = request.headers_mut();
168 headers.insert("ACCESS-KEY", key);
169 headers.insert("ACCESS-TIMESTAMP", HeaderValue::from(timestamp));
170 headers.insert("ACCESS-SIGN", HeaderValue::from_str(&signature).unwrap()); headers.insert(header::CONTENT_TYPE, HeaderValue::from_str("application/json").unwrap()); }
173
174 Ok(request)
175 }
176
177 fn handle_response(&self, status: StatusCode, _: HeaderMap, response_body: Bytes) -> Result<Self::Successful, Self::Unsuccessful> {
178 if status.is_success() {
179 serde_json::from_slice(&response_body).map_err(|error| {
180 log::error!("Failed to parse response due to an error: {}", error);
181 BitFlyerHandlerError::ParseError
182 })
183 } else {
184 let error = match serde_json::from_slice(&response_body) {
185 Ok(parsed_error) => BitFlyerHandlerError::ApiError(parsed_error),
186 Err(error) => {
187 log::error!("Failed to parse error response due to an error: {}", error);
188 BitFlyerHandlerError::ParseError
189 }
190 };
191 Err(error)
192 }
193 }
194}
195
196impl<H> WebSocketHandler for BitFlyerWebSocketHandler<H> where H: FnMut(BitFlyerChannelMessage) + Send + 'static, {
197 fn websocket_config(&self) -> WebSocketConfig {
198 let mut config = self.options.websocket_config.clone();
199 if self.options.websocket_url != BitFlyerWebSocketUrl::None {
200 config.url_prefix = self.options.websocket_url.as_str().to_owned();
201 }
202 config
203 }
204
205 fn handle_start(&mut self) -> Vec<WebSocketMessage> {
206 if self.options.websocket_auth {
207 if let Some(key) = self.options.key.as_deref() {
209 if let Some(secret) = self.options.secret.as_deref() {
210 let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let timestamp = time.as_millis() as u64;
212 let nonce: String = rand::thread_rng()
213 .sample_iter(&Alphanumeric)
214 .take(16)
215 .map(char::from)
216 .collect();
217
218 let mut hmac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap(); hmac.update(format!("{timestamp}{nonce}").as_bytes());
221 let signature = hex::encode(hmac.finalize().into_bytes());
222
223 let id = format!("_auth{}", time.as_nanos());
224 self.auth_id = Some(id.clone());
225
226 return vec![WebSocketMessage::Text(json!({
227 "method": "auth",
228 "params": {
229 "api_key": key,
230 "timestamp": timestamp,
231 "nonce": nonce,
232 "signature": signature,
233 },
234 "id": id,
235 }).to_string())];
236 } else {
237 log::error!("API secret not set.");
238 };
239 } else {
240 log::error!("API key not set.");
241 };
242 }
243 self.message_subscribe()
244 }
245
246 fn handle_message(&mut self, message: WebSocketMessage) -> Vec<WebSocketMessage> {
247 #[derive(Deserialize)]
248 struct Message {
249 #[allow(dead_code)]
250 jsonrpc: String, method: Option<String>,
252 result: Option<serde_json::Value>,
253 params: Option<BitFlyerChannelMessage>,
254 id: Option<String>,
255 }
256
257 match message {
258 WebSocketMessage::Text(message) => {
259 let message: Message = match serde_json::from_str(&message) {
260 Ok(message) => message,
261 Err(_) => {
262 log::warn!("Invalid JSON-RPC message received");
263 return vec![];
264 },
265 };
266 if self.options.websocket_auth && self.auth_id == message.id {
267 if message.result == Some(serde_json::Value::Bool(true)) {
269 log::debug!("WebSocket authentication successful");
270 return self.message_subscribe();
271 } else {
272 log::error!("WebSocket authentication unsuccessful");
273 }
274 self.auth_id = None;
275 } else if message.method.as_deref() == Some("channelMessage") {
276 if let Some(channel_message) = message.params {
277 (self.message_handler)(channel_message);
278 }
279 }
280 },
281 WebSocketMessage::Binary(_) => log::warn!("Unexpected binary message received"),
282 WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) => (),
283 }
284 vec![]
285 }
286}
287
288impl<H> BitFlyerWebSocketHandler<H> where H: FnMut(BitFlyerChannelMessage) + Send + 'static, {
289 #[inline]
290 fn message_subscribe(&self) -> Vec<WebSocketMessage> {
291 self.options.websocket_channels.clone().into_iter().map(|channel| {
292 WebSocketMessage::Text(json!({ "method": "subscribe", "params": { "channel": channel } }).to_string())
293 }).collect()
294 }
295}
296
297impl BitFlyerHttpUrl {
298 #[inline(always)]
300 fn as_str(&self) -> &'static str {
301 match self {
302 Self::Default => "https://api.bitflyer.com",
303 Self::None => "",
304 }
305 }
306}
307
308impl BitFlyerWebSocketUrl {
309 #[inline(always)]
311 fn as_str(&self) -> &'static str {
312 match self {
313 Self::Default => "wss://ws.lightstream.bitflyer.com",
314 Self::None => "",
315 }
316 }
317}
318
319impl HandlerOptions for BitFlyerOptions {
320 type OptionItem = BitFlyerOption;
321
322 fn update(&mut self, option: Self::OptionItem) {
323 match option {
324 BitFlyerOption::Default => (),
325 BitFlyerOption::Key(v) => self.key = Some(v),
326 BitFlyerOption::Secret(v) => self.secret = Some(v),
327 BitFlyerOption::HttpUrl(v) => self.http_url = v,
328 BitFlyerOption::HttpAuth(v) => self.http_auth = v,
329 BitFlyerOption::RequestConfig(v) => self.request_config = v,
330 BitFlyerOption::WebSocketUrl(v) => self.websocket_url = v,
331 BitFlyerOption::WebSocketAuth(v) => self.websocket_auth = v,
332 BitFlyerOption::WebSocketChannels(v) => self.websocket_channels = v,
333 BitFlyerOption::WebSocketConfig(v) => self.websocket_config = v,
334 }
335 }
336}
337
338impl Default for BitFlyerOptions {
339 fn default() -> Self {
340 let mut websocket_config = WebSocketConfig::new();
341 websocket_config.ignore_duplicate_during_reconnection = true;
342 Self {
343 key: None,
344 secret: None,
345 http_url: BitFlyerHttpUrl::Default,
346 http_auth: false,
347 request_config: RequestConfig::default(),
348 websocket_url: BitFlyerWebSocketUrl::Default,
349 websocket_auth: false,
350 websocket_channels: vec![],
351 websocket_config,
352 }
353 }
354}
355
356impl<'a, R: DeserializeOwned + 'a> HttpOption<'a, R> for BitFlyerOption {
357 type RequestHandler = BitFlyerRequestHandler<'a, R>;
358
359 #[inline(always)]
360 fn request_handler(options: Self::Options) -> Self::RequestHandler {
361 BitFlyerRequestHandler::<'a, R> {
362 options,
363 _phantom: PhantomData,
364 }
365 }
366}
367
368impl<H: FnMut(BitFlyerChannelMessage) + Send + 'static> WebSocketOption<H> for BitFlyerOption {
369 type WebSocketHandler = BitFlyerWebSocketHandler<H>;
370
371 #[inline(always)]
372 fn websocket_handler(handler: H, options: Self::Options) -> Self::WebSocketHandler {
373 BitFlyerWebSocketHandler {
374 message_handler: handler,
375 auth_id: None,
376 options,
377 }
378 }
379}
380
381impl HandlerOption for BitFlyerOption {
382 type Options = BitFlyerOptions;
383}
384
385impl Default for BitFlyerOption {
386 fn default() -> Self {
387 Self::Default
388 }
389}