riglr-web-tools 0.3.0

Web-based data tools for riglr agents - Twitter, DexScreener, web search, and more
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
//! # riglr-web-tools
//!
//! Web-based data tools for riglr agents, providing access to social media, market data,
//! and web search capabilities.
//!
//! This crate bridges the gap between on-chain data and off-chain information sources,
//! enabling AI agents to gather comprehensive market intelligence and social sentiment.
//!
//! ## Features
//!
//! - **Social Media Tools**: Twitter/X integration for sentiment analysis
//! - **Market Data Tools**: DexScreener integration for token metrics
//! - **Web Search Tools**: Exa API integration for intelligent web search
//! - **Rate Limiting**: Built-in rate limiting and API quota management
//! - **Caching**: Optional response caching to improve performance
//!
//! ## Quick Start
//!
//! ```ignore
//! // Example usage (requires rig-core dependency):
//! use riglr_web_tools::twitter::search_tweets;
//! use rig_core::Agent;
//!
//! # async fn example() -> anyhow::Result<()> {
//! let agent = Agent::builder()
//!     .preamble("You are a market sentiment analyst.")
//!     .tool(search_tweets)
//!     .build();
//!
//! let response = agent.prompt("What's the current sentiment on Twitter about $SOL?").await?;
//! println!("Agent response: {}", response);
//! # Ok(())
//! # }
//! ```
//!
//! ## API Configuration
//!
//! Most tools require API keys. Set the following environment variables:
//!
//! - `TWITTER_BEARER_TOKEN` - For Twitter API access
//! - `EXA_API_KEY` - For Exa web search
//! - `DEXSCREENER_API_KEY` - For DexScreener (if required)
//! - `LUNARCRUSH_API_KEY` - For LunarCrush social analytics
//! - `FASTER100X_API_KEY` - For Faster100x holder analysis
//!
//! ## Tool Categories
//!
//! - [`twitter`] - Twitter/X integration for social sentiment
//! - [`dexscreener`] - Token market data and trading metrics
//! - [`web_search`] - Intelligent web search capabilities
//! - [`news`] - Cryptocurrency news aggregation
//! - [`lunarcrush`] - LunarCrush social analytics and sentiment tracking
//! - [`faster100x`] - Token holder analysis and whale activity tracking
//! - [`rugcheck`] - Solana token security analysis and rug pull detection
//! - [`trenchbot`] - Solana token bundle analysis and sniper detection
//! - [`pocketuniverse`] - Solana token rug pull detection based on wallet history
//! - [`tweetscout`] - Twitter/X account credibility scoring and social network analysis

pub mod client;
pub mod dexscreener;
pub mod dexscreener_api;
pub mod error;
pub mod faster100x;
pub mod lunarcrush;
pub mod news;
pub mod pocketuniverse;
pub mod price;
pub mod rugcheck;
pub mod trenchbot;
pub mod tweetscout;
pub mod twitter;
pub mod web_search;

// Re-export commonly used tools - be selective to avoid name conflicts
// From dexscreener
pub use dexscreener::{
    analyze_token_market, get_token_info, get_top_pairs, get_trending_tokens, search_tokens,
    ChainInfo, MarketAnalysis, TokenInfo, TokenPair,
};

// From news
pub use news::{
    analyze_market_sentiment, get_crypto_news, get_trending_news, monitor_breaking_news,
    LexiconSentimentAnalyzer, NewsAggregationResult, NewsArticle, NewsSource, SentimentAnalyzer,
};

// From twitter
pub use twitter::{
    analyze_crypto_sentiment, get_user_tweets, search_tweets, SentimentAnalysis,
    SentimentBreakdown, TwitterPost, TwitterSearchResult, TwitterUser,
};

// From web_search
pub use web_search::{
    find_similar_pages, search_recent_news, search_web, summarize_web_content, ContentSummary,
    SearchResult, WebSearchResult,
};

// From lunarcrush
pub use lunarcrush::{
    get_influencer_mentions, get_social_sentiment, get_trending_cryptos, InfluencerMention,
    InfluencerMentionsResult, SentimentData, TrendingCrypto,
};

// From faster100x
pub use faster100x::{
    analyze_token_holders, get_holder_trends, get_whale_activity, ConcentrationRisk, HolderTrends,
    TokenHolderAnalysis, WalletHolding, WhaleActivity,
};

// From price
pub use price::{get_token_price, get_token_prices_batch, TokenPriceResult};

// From rugcheck
pub use rugcheck::{
    analyze_token_risks, check_if_rugged, get_token_report, RiskAnalysis, RiskLevel,
    RugCheckResult, TokenCheck, TokenHolder as RugCheckTokenHolder,
};

// From trenchbot
pub use trenchbot::{
    analyze_creator_risk, analyze_token_bundles, check_bundle_risk, get_bundle_info,
    BundleAnalysisResult, BundleResponse, BundleRiskCheck, CreatorAnalysisResult,
};

// From pocketuniverse
pub use pocketuniverse::{
    analyze_rug_risk, check_rug_pull, check_rug_pull_raw, is_token_safe, DetailedRugAnalysis,
    RugApiResponse, RugCheckResult as PocketUniverseRugCheck, SafetyCheck,
};

// From tweetscout
pub use tweetscout::{
    analyze_account, analyze_social_network, get_account_info, get_account_score,
    get_top_followers, get_top_friends, is_account_credible, AccountAnalysis, AccountInfo,
    CredibilityCheck, SocialNetworkAnalysis,
};

// Re-export client and error types
pub use client::WebClient;
pub use error::{Result, WebToolError};

/// Current version of riglr-web-tools
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_version_when_valid_should_start_with_semver_digit() {
        // Happy Path: VERSION should be a valid semver
        assert!(
            VERSION.starts_with("0.") || VERSION.starts_with("1."),
            "VERSION should be a valid semver"
        );
    }

    #[test]
    fn test_version_when_called_should_not_be_empty() {
        // Edge Case: VERSION should not be empty
        assert!(!VERSION.is_empty(), "VERSION should not be empty");
    }

    #[test]
    fn test_version_when_called_should_contain_dots() {
        // Edge Case: VERSION should contain dots for semver format
        assert!(
            VERSION.contains('.'),
            "VERSION should contain dots for semver format"
        );
    }

    #[test]
    fn test_version_when_called_should_be_valid_utf8() {
        // Edge Case: VERSION should be valid UTF-8
        assert!(VERSION.is_ascii(), "VERSION should be valid ASCII");
    }

    #[test]
    fn test_version_when_called_should_match_cargo_version() {
        // Integration test: VERSION should match what's in Cargo.toml
        let version = env!("CARGO_PKG_VERSION");
        assert_eq!(VERSION, version, "VERSION should match CARGO_PKG_VERSION");
    }

    #[test]
    fn test_version_when_parsed_should_have_major_minor_patch() {
        // Edge Case: VERSION should have at least major.minor format
        let parts: Vec<&str> = VERSION.split('.').collect();
        assert!(
            parts.len() >= 2,
            "VERSION should have at least major.minor format"
        );

        // Verify major version is numeric
        assert!(
            parts[0].parse::<u32>().is_ok(),
            "Major version should be numeric"
        );

        // Verify minor version is numeric (may contain pre-release info)
        let minor_part = parts[1].split('-').next().unwrap_or(parts[1]);
        assert!(
            minor_part.parse::<u32>().is_ok(),
            "Minor version should be numeric"
        );
    }

    #[test]
    fn test_module_re_exports_are_accessible() {
        // Test that re-exported types are accessible
        // This ensures the pub use statements work correctly

        // Test a few key re-exports from different modules
        use crate::{Result, WebClient, WebToolError, VERSION};

        // These should compile without issues, proving the re-exports work
        let _version: &str = VERSION;
        let _error_type = std::marker::PhantomData::<WebToolError>;
        let _result_type = std::marker::PhantomData::<Result<()>>;
        let _client_type = std::marker::PhantomData::<WebClient>;
    }

    #[test]
    fn test_dexscreener_re_exports_are_accessible() {
        // Test dexscreener re-exports
        use crate::{ChainInfo, MarketAnalysis, TokenInfo, TokenPair};

        let _chain_info_type = std::marker::PhantomData::<ChainInfo>;
        let _market_analysis_type = std::marker::PhantomData::<MarketAnalysis>;
        let _token_info_type = std::marker::PhantomData::<TokenInfo>;
        let _token_pair_type = std::marker::PhantomData::<TokenPair>;
    }

    #[test]
    fn test_news_re_exports_are_accessible() {
        // Test news re-exports
        use crate::{NewsAggregationResult, NewsArticle, NewsSource};

        let _news_aggregation_type = std::marker::PhantomData::<NewsAggregationResult>;
        let _news_article_type = std::marker::PhantomData::<NewsArticle>;
        let _news_source_type = std::marker::PhantomData::<NewsSource>;
    }

    #[test]
    fn test_twitter_re_exports_are_accessible() {
        // Test twitter re-exports
        use crate::{
            SentimentAnalysis, SentimentBreakdown, TwitterPost, TwitterSearchResult, TwitterUser,
        };

        let _sentiment_analysis_type = std::marker::PhantomData::<SentimentAnalysis>;
        let _sentiment_breakdown_type = std::marker::PhantomData::<SentimentBreakdown>;
        let _twitter_post_type = std::marker::PhantomData::<TwitterPost>;
        let _twitter_search_result_type = std::marker::PhantomData::<TwitterSearchResult>;
        let _twitter_user_type = std::marker::PhantomData::<TwitterUser>;
    }

    #[test]
    fn test_web_search_re_exports_are_accessible() {
        // Test web_search re-exports
        use crate::{ContentSummary, SearchResult, WebSearchResult};

        let _content_summary_type = std::marker::PhantomData::<ContentSummary>;
        let _search_result_type = std::marker::PhantomData::<SearchResult>;
        let _web_search_result_type = std::marker::PhantomData::<WebSearchResult>;
    }

    #[test]
    fn test_lunarcrush_re_exports_are_accessible() {
        // Test lunarcrush re-exports
        use crate::{InfluencerMention, InfluencerMentionsResult, SentimentData, TrendingCrypto};

        let _influencer_mention_type = std::marker::PhantomData::<InfluencerMention>;
        let _influencer_mentions_result_type = std::marker::PhantomData::<InfluencerMentionsResult>;
        let _sentiment_data_type = std::marker::PhantomData::<SentimentData>;
        let _trending_crypto_type = std::marker::PhantomData::<TrendingCrypto>;
    }

    #[test]
    fn test_faster100x_re_exports_are_accessible() {
        // Test faster100x re-exports
        use crate::{
            ConcentrationRisk, HolderTrends, TokenHolderAnalysis, WalletHolding, WhaleActivity,
        };

        let _concentration_risk_type = std::marker::PhantomData::<ConcentrationRisk>;
        let _holder_trends_type = std::marker::PhantomData::<HolderTrends>;
        let _token_holder_analysis_type = std::marker::PhantomData::<TokenHolderAnalysis>;
        let _wallet_holding_type = std::marker::PhantomData::<WalletHolding>;
        let _whale_activity_type = std::marker::PhantomData::<WhaleActivity>;
    }

    #[test]
    fn test_price_re_exports_are_accessible() {
        // Test price re-exports
        use crate::TokenPriceResult;

        let _token_price_result_type = std::marker::PhantomData::<TokenPriceResult>;
    }

    #[test]
    fn test_rugcheck_re_exports_are_accessible() {
        // Test rugcheck re-exports
        use crate::{RiskAnalysis, RiskLevel, RugCheckResult, RugCheckTokenHolder, TokenCheck};

        let _risk_analysis_type = std::marker::PhantomData::<RiskAnalysis>;
        let _risk_level_type = std::marker::PhantomData::<RiskLevel>;
        let _rugcheck_result_type = std::marker::PhantomData::<RugCheckResult>;
        let _token_check_type = std::marker::PhantomData::<TokenCheck>;
        let _rugcheck_token_holder_type = std::marker::PhantomData::<RugCheckTokenHolder>;
    }

    #[test]
    fn test_trenchbot_re_exports_are_accessible() {
        // Test trenchbot re-exports
        use crate::{BundleAnalysisResult, BundleResponse, BundleRiskCheck, CreatorAnalysisResult};

        let _bundle_analysis_result_type = std::marker::PhantomData::<BundleAnalysisResult>;
        let _bundle_response_type = std::marker::PhantomData::<BundleResponse>;
        let _bundle_risk_check_type = std::marker::PhantomData::<BundleRiskCheck>;
        let _creator_analysis_result_type = std::marker::PhantomData::<CreatorAnalysisResult>;
    }

    #[test]
    fn test_pocketuniverse_re_exports_are_accessible() {
        // Test pocketuniverse re-exports
        use crate::{DetailedRugAnalysis, PocketUniverseRugCheck, RugApiResponse, SafetyCheck};

        let _detailed_rug_analysis_type = std::marker::PhantomData::<DetailedRugAnalysis>;
        let _pocket_universe_rug_check_type = std::marker::PhantomData::<PocketUniverseRugCheck>;
        let _rug_api_response_type = std::marker::PhantomData::<RugApiResponse>;
        let _safety_check_type = std::marker::PhantomData::<SafetyCheck>;
    }

    #[test]
    fn test_tweetscout_re_exports_are_accessible() {
        // Test tweetscout re-exports
        use crate::{AccountAnalysis, AccountInfo, CredibilityCheck, SocialNetworkAnalysis};

        let _account_analysis_type = std::marker::PhantomData::<AccountAnalysis>;
        let _account_info_type = std::marker::PhantomData::<AccountInfo>;
        let _credibility_check_type = std::marker::PhantomData::<CredibilityCheck>;
        let _social_network_analysis_type = std::marker::PhantomData::<SocialNetworkAnalysis>;
    }

    #[test]
    fn test_all_function_re_exports_are_accessible() {
        // Test that function re-exports are accessible (compile-time check)
        use crate::{
            analyze_account, analyze_creator_risk, analyze_crypto_sentiment,
            analyze_market_sentiment, analyze_rug_risk, analyze_social_network,
            analyze_token_bundles, analyze_token_holders, analyze_token_market,
            analyze_token_risks, check_bundle_risk, check_if_rugged, check_rug_pull,
            check_rug_pull_raw, find_similar_pages, get_account_info, get_account_score,
            get_bundle_info, get_crypto_news, get_holder_trends, get_influencer_mentions,
            get_social_sentiment, get_token_info, get_token_price, get_token_prices_batch,
            get_token_report, get_top_followers, get_top_friends, get_top_pairs,
            get_trending_cryptos, get_trending_news, get_trending_tokens, get_user_tweets,
            get_whale_activity, is_account_credible, is_token_safe, monitor_breaking_news,
            search_recent_news, search_tokens, search_tweets, search_web, summarize_web_content,
        };

        // These functions are async and return impl Future, so we can't cast them to simple function pointers.
        // Instead, we verify they exist by referencing them as function items
        let _analyze_token_market = analyze_token_market;
        let _get_token_info = get_token_info;
        let _get_top_pairs = get_top_pairs;
        let _get_trending_tokens = get_trending_tokens;
        let _search_tokens = search_tokens;

        let _analyze_market_sentiment = analyze_market_sentiment;
        let _get_crypto_news = get_crypto_news;
        let _get_trending_news = get_trending_news;
        let _monitor_breaking_news = monitor_breaking_news;

        let _analyze_crypto_sentiment = analyze_crypto_sentiment;
        let _get_user_tweets = get_user_tweets;
        let _search_tweets = search_tweets;

        let _find_similar_pages = find_similar_pages;
        let _search_recent_news = search_recent_news;
        let _search_web = search_web;
        let _summarize_web_content = summarize_web_content;

        let _get_influencer_mentions = get_influencer_mentions;
        let _get_social_sentiment = get_social_sentiment;
        let _get_trending_cryptos = get_trending_cryptos;

        let _analyze_token_holders = analyze_token_holders;
        let _get_holder_trends = get_holder_trends;
        let _get_whale_activity = get_whale_activity;

        let _get_token_price = get_token_price;
        let _get_token_prices_batch = get_token_prices_batch;

        let _analyze_token_risks = analyze_token_risks;
        let _check_if_rugged = check_if_rugged;
        let _get_token_report = get_token_report;

        let _analyze_creator_risk = analyze_creator_risk;
        let _analyze_token_bundles = analyze_token_bundles;
        let _check_bundle_risk = check_bundle_risk;
        let _get_bundle_info = get_bundle_info;

        let _analyze_rug_risk = analyze_rug_risk;
        let _check_rug_pull = check_rug_pull;
        let _check_rug_pull_raw = check_rug_pull_raw;
        let _is_token_safe = is_token_safe;

        let _analyze_account = analyze_account;
        let _analyze_social_network = analyze_social_network;
        let _get_account_info = get_account_info;
        let _get_account_score = get_account_score;
        let _get_top_followers = get_top_followers;
        let _get_top_friends = get_top_friends;
        let _is_account_credible = is_account_credible;
    }

    #[test]
    fn test_version_constant_is_static() {
        // Test that VERSION is a static string reference
        let version_ref: &'static str = VERSION;
        assert!(!version_ref.is_empty(), "VERSION should not be empty");
    }

    #[test]
    fn test_module_declarations_are_public() {
        // This test verifies that all the modules are properly declared as public
        // by attempting to access module paths (compile-time verification)

        // Test that all modules can be referenced
        let _client_mod = crate::client::WebClient::default;
        let _dexscreener_mod = crate::dexscreener::get_token_info;
        // dexscreener_api module contains utility functions and types, not a main struct
        let _error_mod = std::marker::PhantomData::<crate::error::WebToolError>;
        let _faster100x_mod = crate::faster100x::analyze_token_holders;
        let _lunarcrush_mod = crate::lunarcrush::get_social_sentiment;
        let _news_mod = crate::news::get_crypto_news;
        let _pocketuniverse_mod = crate::pocketuniverse::check_rug_pull;
        let _price_mod = crate::price::get_token_price;
        let _rugcheck_mod = crate::rugcheck::get_token_report;
        let _trenchbot_mod = crate::trenchbot::get_bundle_info;
        let _tweetscout_mod = crate::tweetscout::get_account_info;
        let _twitter_mod = crate::twitter::search_tweets;
        let _web_search_mod = crate::web_search::search_web;
    }
}