snm_brightdata_client/tools/
crypto.rs

1// src/tools/crypto.rs
2use crate::tool::{Tool, ToolResult, McpContent};
3use crate::error::BrightDataError;
4use async_trait::async_trait;
5use reqwest::Client;
6use serde_json::{json, Value};
7use std::env;
8use std::time::Duration;
9
10pub struct CryptoDataTool;
11
12#[async_trait]
13impl Tool for CryptoDataTool {
14    fn name(&self) -> &str {
15        "get_crypto_data"
16    }
17
18    fn description(&self) -> &str {
19        "Get cryptocurrency data including prices, market cap, trading volumes"
20    }
21
22    fn input_schema(&self) -> Value {
23        json!({
24            "type": "object",
25            "properties": {
26                "query": {
27                    "type": "string",
28                    "description": "Crypto symbol (BTC, ETH, ADA), crypto name (Bitcoin, Ethereum), comparison query (BTC vs ETH), or market overview (crypto market today, top cryptocurrencies)"
29                }
30            },
31            "required": ["query"]
32        })
33    }
34
35    async fn execute(&self, parameters: Value) -> Result<ToolResult, BrightDataError> {
36        let query = parameters
37            .get("query")
38            .and_then(|v| v.as_str())
39            .ok_or_else(|| BrightDataError::ToolError("Missing 'query' parameter".into()))?;
40
41        let result = self.fetch_crypto_data(query).await?;
42
43        let content_text = result.get("content").and_then(|c| c.as_str()).unwrap_or("No crypto data found");
44        let mcp_content = vec![McpContent::text(format!(
45            "₿ **Cryptocurrency Data for {}**\n\n{}",
46            query,
47            content_text
48        ))];
49
50        Ok(ToolResult::success_with_raw(mcp_content, result))
51    }
52}
53
54impl CryptoDataTool {
55    async fn fetch_crypto_data(&self, query: &str) -> Result<Value, BrightDataError> {
56        let api_token = env::var("BRIGHTDATA_API_TOKEN")
57            .or_else(|_| env::var("API_TOKEN"))
58            .map_err(|_| BrightDataError::ToolError("Missing BRIGHTDATA_API_TOKEN".into()))?;
59
60        let base_url = env::var("BRIGHTDATA_BASE_URL")
61            .unwrap_or_else(|_| "https://api.brightdata.com".to_string());
62
63        let zone = env::var("WEB_UNLOCKER_ZONE")
64            .unwrap_or_else(|_| "default".to_string());
65
66        // Build crypto-specific search URL
67        let search_url = format!(
68            "https://www.google.com/search?q={} cryptocurrency price market cap coinmarketcap",
69            urlencoding::encode(query)
70        );
71
72        let payload = json!({
73            "url": search_url,
74            "zone": zone,
75            "format": "raw",
76            "data_format": "markdown"
77        });
78
79        let client = Client::builder()
80            .timeout(Duration::from_secs(120))
81            .build()
82            .map_err(|e| BrightDataError::ToolError(e.to_string()))?;
83
84        let response = client
85            .post(&format!("{}/request", base_url))
86            .header("Authorization", format!("Bearer {}", api_token))
87            .header("Content-Type", "application/json")
88            .json(&payload)
89            .send()
90            .await
91            .map_err(|e| BrightDataError::ToolError(format!("Crypto data request failed: {}", e)))?;
92
93        let status = response.status();
94        if !status.is_success() {
95            let error_text = response.text().await.unwrap_or_default();
96            return Err(BrightDataError::ToolError(format!(
97                "BrightData crypto data error {}: {}",
98                status, error_text
99            )));
100        }
101
102        let content = response.text().await
103            .map_err(|e| BrightDataError::ToolError(e.to_string()))?;
104
105        Ok(json!({
106            "content": content,
107            "query": query,
108            "success": true
109        }))
110    }
111}