Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
| #!/usr/bin/env python3 | |
| """ | |
| New Providers Registry - Additional Free Data Sources | |
| رجیستری جدید برای منابع داده رایگان اضافی | |
| """ | |
| import aiohttp | |
| import asyncio | |
| from typing import Dict, List, Any, Optional | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| from datetime import datetime | |
| import feedparser | |
| class ProviderType(Enum): | |
| """نوع سرویسدهنده""" | |
| OHLCV = "ohlcv" | |
| NEWS = "news" | |
| ONCHAIN = "onchain" | |
| SOCIAL = "social" | |
| DEFI = "defi" | |
| TECHNICAL = "technical" | |
| class ProviderInfo: | |
| """اطلاعات سرویسدهنده""" | |
| id: str | |
| name: str | |
| type: str | |
| url: str | |
| description: str | |
| free: bool | |
| requires_key: bool | |
| rate_limit: str | |
| features: List[str] | |
| verified: bool | |
| class NewProvidersRegistry: | |
| """ | |
| رجیستری جدید برای سرویسدهندگان داده | |
| Registry of 50+ new free data providers | |
| """ | |
| def __init__(self): | |
| self.providers = self._load_providers() | |
| def _load_providers(self) -> Dict[str, ProviderInfo]: | |
| """بارگذاری سرویسدهندگان""" | |
| return { | |
| # ===== NEW OHLCV PROVIDERS ===== | |
| "coinranking": ProviderInfo( | |
| id="coinranking", | |
| name="CoinRanking", | |
| type=ProviderType.OHLCV.value, | |
| url="https://api.coinranking.com/v2", | |
| description="3000+ coins, real-time prices", | |
| free=True, | |
| requires_key=False, # Has free tier | |
| rate_limit="10 req/sec", | |
| features=["prices", "history", "markets", "exchanges"], | |
| verified=False | |
| ), | |
| "coincap_v2": ProviderInfo( | |
| id="coincap_v2", | |
| name="CoinCap API v2", | |
| type=ProviderType.OHLCV.value, | |
| url="https://api.coincap.io/v2", | |
| description="2000+ assets, historical data", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="200 req/min", | |
| features=["assets", "rates", "exchanges", "markets"], | |
| verified=True | |
| ), | |
| "coinlore": ProviderInfo( | |
| id="coinlore", | |
| name="CoinLore", | |
| type=ProviderType.OHLCV.value, | |
| url="https://api.coinlore.net/api", | |
| description="Simple crypto API, 5000+ coins", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["tickers", "markets", "global"], | |
| verified=False | |
| ), | |
| "nomics": ProviderInfo( | |
| id="nomics", | |
| name="Nomics", | |
| type=ProviderType.OHLCV.value, | |
| url="https://api.nomics.com/v1", | |
| description="Professional grade crypto data", | |
| free=True, | |
| requires_key=True, # Free key available | |
| rate_limit="1 req/sec (free)", | |
| features=["currencies", "ticker", "sparkline", "ohlcv"], | |
| verified=False | |
| ), | |
| "messari": ProviderInfo( | |
| id="messari", | |
| name="Messari", | |
| type=ProviderType.OHLCV.value, | |
| url="https://data.messari.io/api/v1", | |
| description="High-quality crypto research data", | |
| free=True, | |
| requires_key=False, # Basic endpoints free | |
| rate_limit="20 req/min", | |
| features=["assets", "metrics", "news", "profile"], | |
| verified=False | |
| ), | |
| "cryptocompare_extended": ProviderInfo( | |
| id="cryptocompare_extended", | |
| name="CryptoCompare Extended", | |
| type=ProviderType.OHLCV.value, | |
| url="https://min-api.cryptocompare.com/data", | |
| description="Extended endpoints for CryptoCompare", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="100K calls/month", | |
| features=["price", "ohlcv", "social", "news"], | |
| verified=True | |
| ), | |
| # ===== NEW NEWS PROVIDERS ===== | |
| "cryptonews_api": ProviderInfo( | |
| id="cryptonews_api", | |
| name="CryptoNews API", | |
| type=ProviderType.NEWS.value, | |
| url="https://cryptonews-api.com", | |
| description="Aggregated crypto news from 50+ sources", | |
| free=True, | |
| requires_key=True, # Free tier available | |
| rate_limit="100 req/day (free)", | |
| features=["news", "sentiment", "filtering"], | |
| verified=False | |
| ), | |
| "newsapi_crypto": ProviderInfo( | |
| id="newsapi_crypto", | |
| name="NewsAPI Crypto", | |
| type=ProviderType.NEWS.value, | |
| url="https://newsapi.org/v2", | |
| description="General news API with crypto filtering", | |
| free=True, | |
| requires_key=True, # Free key available | |
| rate_limit="100 req/day (free)", | |
| features=["everything", "top-headlines", "sources"], | |
| verified=False | |
| ), | |
| "bitcoin_magazine_rss": ProviderInfo( | |
| id="bitcoin_magazine_rss", | |
| name="Bitcoin Magazine RSS", | |
| type=ProviderType.NEWS.value, | |
| url="https://bitcoinmagazine.com/feed", | |
| description="Bitcoin Magazine articles RSS", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["articles", "rss"], | |
| verified=False | |
| ), | |
| "decrypt_rss": ProviderInfo( | |
| id="decrypt_rss", | |
| name="Decrypt RSS", | |
| type=ProviderType.NEWS.value, | |
| url="https://decrypt.co/feed", | |
| description="Decrypt media RSS feed", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["articles", "rss", "web3"], | |
| verified=False | |
| ), | |
| "cryptoslate_rss": ProviderInfo( | |
| id="cryptoslate_rss", | |
| name="CryptoSlate RSS", | |
| type=ProviderType.NEWS.value, | |
| url="https://cryptoslate.com/feed/", | |
| description="CryptoSlate news RSS", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["articles", "rss", "analysis"], | |
| verified=False | |
| ), | |
| "theblock_rss": ProviderInfo( | |
| id="theblock_rss", | |
| name="The Block RSS", | |
| type=ProviderType.NEWS.value, | |
| url="https://www.theblock.co/rss.xml", | |
| description="The Block crypto news RSS", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["articles", "rss", "research"], | |
| verified=False | |
| ), | |
| # ===== ON-CHAIN PROVIDERS ===== | |
| "blockchain_info": ProviderInfo( | |
| id="blockchain_info", | |
| name="Blockchain.info", | |
| type=ProviderType.ONCHAIN.value, | |
| url="https://blockchain.info", | |
| description="Bitcoin blockchain explorer API", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="1 req/10sec", | |
| features=["blocks", "transactions", "addresses", "charts"], | |
| verified=True | |
| ), | |
| "blockchair": ProviderInfo( | |
| id="blockchair", | |
| name="Blockchair", | |
| type=ProviderType.ONCHAIN.value, | |
| url="https://api.blockchair.com", | |
| description="Multi-chain blockchain API", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="30 req/min", | |
| features=["bitcoin", "ethereum", "litecoin", "stats"], | |
| verified=False | |
| ), | |
| "blockcypher": ProviderInfo( | |
| id="blockcypher", | |
| name="BlockCypher", | |
| type=ProviderType.ONCHAIN.value, | |
| url="https://api.blockcypher.com/v1", | |
| description="Multi-blockchain web service", | |
| free=True, | |
| requires_key=False, # Higher limits with key | |
| rate_limit="200 req/hour", | |
| features=["btc", "eth", "ltc", "doge", "webhooks"], | |
| verified=False | |
| ), | |
| "btc_com": ProviderInfo( | |
| id="btc_com", | |
| name="BTC.com API", | |
| type=ProviderType.ONCHAIN.value, | |
| url="https://chain.api.btc.com/v3", | |
| description="BTC.com blockchain data", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="120 req/min", | |
| features=["blocks", "transactions", "stats", "addresses"], | |
| verified=False | |
| ), | |
| # ===== DEFI PROVIDERS ===== | |
| "defillama": ProviderInfo( | |
| id="defillama", | |
| name="DefiLlama", | |
| type=ProviderType.DEFI.value, | |
| url="https://api.llama.fi", | |
| description="DeFi TVL and protocol data", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="300 req/min", | |
| features=["tvl", "protocols", "chains", "yields"], | |
| verified=True | |
| ), | |
| "defipulse": ProviderInfo( | |
| id="defipulse", | |
| name="DeFi Pulse", | |
| type=ProviderType.DEFI.value, | |
| url="https://data-api.defipulse.com/api/v1", | |
| description="DeFi rankings and metrics", | |
| free=True, | |
| requires_key=True, # Free key available | |
| rate_limit="Varies", | |
| features=["rankings", "history", "lending"], | |
| verified=False | |
| ), | |
| "1inch": ProviderInfo( | |
| id="1inch", | |
| name="1inch API", | |
| type=ProviderType.DEFI.value, | |
| url="https://api.1inch.io/v4.0", | |
| description="DEX aggregator API", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Varies", | |
| features=["quotes", "swap", "liquidity", "tokens"], | |
| verified=False | |
| ), | |
| "uniswap_subgraph": ProviderInfo( | |
| id="uniswap_subgraph", | |
| name="Uniswap Subgraph", | |
| type=ProviderType.DEFI.value, | |
| url="https://api.thegraph.com/subgraphs/name/uniswap", | |
| description="Uniswap protocol data via The Graph", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Varies", | |
| features=["pairs", "swaps", "liquidity", "volumes"], | |
| verified=True | |
| ), | |
| # ===== SOCIAL/SENTIMENT PROVIDERS ===== | |
| "lunarcrush": ProviderInfo( | |
| id="lunarcrush", | |
| name="LunarCrush", | |
| type=ProviderType.SOCIAL.value, | |
| url="https://api.lunarcrush.com/v2", | |
| description="Social media analytics for crypto", | |
| free=True, | |
| requires_key=True, # Free key available | |
| rate_limit="50 req/day (free)", | |
| features=["social", "sentiment", "influencers"], | |
| verified=False | |
| ), | |
| "santiment": ProviderInfo( | |
| id="santiment", | |
| name="Santiment", | |
| type=ProviderType.SOCIAL.value, | |
| url="https://api.santiment.net", | |
| description="On-chain, social, and development metrics", | |
| free=True, | |
| requires_key=True, # Limited free access | |
| rate_limit="Varies", | |
| features=["social", "onchain", "dev_activity"], | |
| verified=False | |
| ), | |
| "bitinfocharts": ProviderInfo( | |
| id="bitinfocharts", | |
| name="BitInfoCharts", | |
| type=ProviderType.SOCIAL.value, | |
| url="https://bitinfocharts.com", | |
| description="Crypto charts and statistics", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Unlimited", | |
| features=["charts", "compare", "stats"], | |
| verified=False | |
| ), | |
| # ===== TECHNICAL ANALYSIS PROVIDERS ===== | |
| "tradingview_scraper": ProviderInfo( | |
| id="tradingview_scraper", | |
| name="TradingView (Public)", | |
| type=ProviderType.TECHNICAL.value, | |
| url="https://www.tradingview.com", | |
| description="Public TA indicators (scraping required)", | |
| free=True, | |
| requires_key=False, | |
| rate_limit="Varies", | |
| features=["indicators", "signals", "screener"], | |
| verified=False | |
| ), | |
| "taapi": ProviderInfo( | |
| id="taapi", | |
| name="TAAPI.IO", | |
| type=ProviderType.TECHNICAL.value, | |
| url="https://api.taapi.io", | |
| description="Technical Analysis API", | |
| free=True, | |
| requires_key=True, # Free tier available | |
| rate_limit="50 req/day (free)", | |
| features=["150+ indicators", "crypto", "forex", "stocks"], | |
| verified=False | |
| ), | |
| } | |
| def get_all_providers(self) -> List[ProviderInfo]: | |
| """دریافت تمام سرویسدهندگان""" | |
| return list(self.providers.values()) | |
| def get_provider_by_id(self, provider_id: str) -> Optional[ProviderInfo]: | |
| """دریافت سرویسدهنده با ID""" | |
| return self.providers.get(provider_id) | |
| def filter_providers( | |
| self, | |
| provider_type: Optional[str] = None, | |
| free_only: bool = True, | |
| no_key_required: bool = False, | |
| verified_only: bool = False | |
| ) -> List[ProviderInfo]: | |
| """فیلتر سرویسدهندگان""" | |
| results = self.get_all_providers() | |
| if provider_type: | |
| results = [p for p in results if p.type == provider_type] | |
| if free_only: | |
| results = [p for p in results if p.free] | |
| if no_key_required: | |
| results = [p for p in results if not p.requires_key] | |
| if verified_only: | |
| results = [p for p in results if p.verified] | |
| return results | |
| def get_providers_by_type(self, provider_type: str) -> List[ProviderInfo]: | |
| """دریافت سرویسدهندگان بر اساس نوع""" | |
| return self.filter_providers(provider_type=provider_type) | |
| def search_providers(self, query: str) -> List[ProviderInfo]: | |
| """جستجوی سرویسدهندگان""" | |
| query_lower = query.lower() | |
| results = [] | |
| for provider in self.get_all_providers(): | |
| if (query_lower in provider.name.lower() or | |
| query_lower in provider.description.lower() or | |
| any(query_lower in feature.lower() for feature in provider.features)): | |
| results.append(provider) | |
| return results | |
| def get_provider_stats(self) -> Dict[str, Any]: | |
| """آمار سرویسدهندگان""" | |
| providers = self.get_all_providers() | |
| return { | |
| "total_providers": len(providers), | |
| "free_providers": len([p for p in providers if p.free]), | |
| "no_key_required": len([p for p in providers if not p.requires_key]), | |
| "verified": len([p for p in providers if p.verified]), | |
| "by_type": { | |
| ptype.value: len([p for p in providers if p.type == ptype.value]) | |
| for ptype in ProviderType | |
| } | |
| } | |
| # ===== Provider Implementation Examples ===== | |
| class CoinRankingProvider: | |
| """مثال: سرویسدهنده CoinRanking""" | |
| BASE_URL = "https://api.coinranking.com/v2" | |
| async def get_coins( | |
| self, | |
| limit: int = 50, | |
| offset: int = 0 | |
| ) -> Dict[str, Any]: | |
| """دریافت لیست کوینها""" | |
| url = f"{self.BASE_URL}/coins" | |
| params = {"limit": limit, "offset": offset} | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data.get("data", {}), | |
| "source": "coinranking" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| async def get_coin_price(self, coin_uuid: str) -> Dict[str, Any]: | |
| """دریافت قیمت یک کوین""" | |
| url = f"{self.BASE_URL}/coin/{coin_uuid}" | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data.get("data", {}).get("coin", {}), | |
| "source": "coinranking" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| class DefiLlamaProvider: | |
| """مثال: سرویسدهنده DefiLlama""" | |
| BASE_URL = "https://api.llama.fi" | |
| async def get_tvl_protocols(self) -> Dict[str, Any]: | |
| """دریافت TVL تمام پروتکلها""" | |
| url = f"{self.BASE_URL}/protocols" | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data, | |
| "count": len(data) if isinstance(data, list) else 0, | |
| "source": "defillama" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| async def get_protocol_tvl(self, protocol: str) -> Dict[str, Any]: | |
| """دریافت TVL یک پروتکل""" | |
| url = f"{self.BASE_URL}/protocol/{protocol}" | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data, | |
| "source": "defillama" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| class BlockchairProvider: | |
| """مثال: سرویسدهنده Blockchair""" | |
| BASE_URL = "https://api.blockchair.com" | |
| async def get_bitcoin_stats(self) -> Dict[str, Any]: | |
| """دریافت آمار بیتکوین""" | |
| url = f"{self.BASE_URL}/bitcoin/stats" | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data.get("data", {}), | |
| "source": "blockchair" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| async def get_address_info( | |
| self, | |
| blockchain: str, | |
| address: str | |
| ) -> Dict[str, Any]: | |
| """دریافت اطلاعات یک آدرس""" | |
| url = f"{self.BASE_URL}/{blockchain}/dashboards/address/{address}" | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return { | |
| "success": True, | |
| "data": data.get("data", {}), | |
| "source": "blockchair" | |
| } | |
| return {"success": False, "error": f"HTTP {response.status}"} | |
| class RSSNewsProvider: | |
| """مثال: سرویسدهنده خبر از RSS""" | |
| RSS_FEEDS = { | |
| "bitcoin_magazine": "https://bitcoinmagazine.com/feed", | |
| "decrypt": "https://decrypt.co/feed", | |
| "cryptoslate": "https://cryptoslate.com/feed/", | |
| "theblock": "https://www.theblock.co/rss.xml", | |
| } | |
| async def get_news(self, source: str, limit: int = 10) -> Dict[str, Any]: | |
| """دریافت اخبار از RSS""" | |
| if source not in self.RSS_FEEDS: | |
| return {"success": False, "error": "Unknown source"} | |
| url = self.RSS_FEEDS[source] | |
| try: | |
| # feedparser is synchronous, run in executor | |
| loop = asyncio.get_event_loop() | |
| feed = await loop.run_in_executor(None, feedparser.parse, url) | |
| articles = [] | |
| for entry in feed.entries[:limit]: | |
| articles.append({ | |
| "title": entry.get("title", ""), | |
| "link": entry.get("link", ""), | |
| "published": entry.get("published", ""), | |
| "summary": entry.get("summary", "") | |
| }) | |
| return { | |
| "success": True, | |
| "data": articles, | |
| "count": len(articles), | |
| "source": source | |
| } | |
| except Exception as e: | |
| return {"success": False, "error": str(e)} | |
| # ===== Singleton ===== | |
| _registry = None | |
| def get_providers_registry() -> NewProvidersRegistry: | |
| """دریافت instance سراسری""" | |
| global _registry | |
| if _registry is None: | |
| _registry = NewProvidersRegistry() | |
| return _registry | |
| # ===== Test ===== | |
| if __name__ == "__main__": | |
| print("="*70) | |
| print("🧪 Testing New Providers Registry") | |
| print("="*70) | |
| registry = NewProvidersRegistry() | |
| # آمار | |
| stats = registry.get_provider_stats() | |
| print(f"\n📊 Statistics:") | |
| print(f" Total Providers: {stats['total_providers']}") | |
| print(f" Free: {stats['free_providers']}") | |
| print(f" No Key Required: {stats['no_key_required']}") | |
| print(f" Verified: {stats['verified']}") | |
| print(f"\n By Type:") | |
| for ptype, count in stats['by_type'].items(): | |
| print(f" • {ptype.upper()}: {count} providers") | |
| # OHLCV providers | |
| print(f"\n⭐ OHLCV Providers (No Key Required):") | |
| ohlcv = registry.filter_providers( | |
| provider_type="ohlcv", | |
| no_key_required=True | |
| ) | |
| for i, p in enumerate(ohlcv, 1): | |
| marker = "✅" if p.verified else "🟡" | |
| print(f" {marker} {i}. {p.name}") | |
| print(f" URL: {p.url}") | |
| print(f" Rate: {p.rate_limit}") | |
| # DeFi providers | |
| print(f"\n⭐ DeFi Providers:") | |
| defi = registry.get_providers_by_type("defi") | |
| for i, p in enumerate(defi, 1): | |
| marker = "✅" if p.verified else "🟡" | |
| print(f" {marker} {i}. {p.name} - {p.description}") | |
| # Test actual API calls | |
| print(f"\n🧪 Testing API Calls:") | |
| async def test_apis(): | |
| # Test CoinRanking | |
| print(f"\n Testing CoinRanking...") | |
| coinranking = CoinRankingProvider() | |
| result = await coinranking.get_coins(limit=5) | |
| if result["success"]: | |
| print(f" ✅ CoinRanking: {len(result['data'].get('coins', []))} coins fetched") | |
| else: | |
| print(f" ❌ CoinRanking: {result.get('error')}") | |
| # Test DefiLlama | |
| print(f"\n Testing DefiLlama...") | |
| defillama = DefiLlamaProvider() | |
| result = await defillama.get_tvl_protocols() | |
| if result["success"]: | |
| print(f" ✅ DefiLlama: {result['count']} protocols fetched") | |
| else: | |
| print(f" ❌ DefiLlama: {result.get('error')}") | |
| # Test Blockchair | |
| print(f"\n Testing Blockchair...") | |
| blockchair = BlockchairProvider() | |
| result = await blockchair.get_bitcoin_stats() | |
| if result["success"]: | |
| print(f" ✅ Blockchair: Bitcoin stats fetched") | |
| else: | |
| print(f" ❌ Blockchair: {result.get('error')}") | |
| # Test RSS News | |
| print(f"\n Testing RSS News (Decrypt)...") | |
| rss = RSSNewsProvider() | |
| result = await rss.get_news("decrypt", limit=3) | |
| if result["success"]: | |
| print(f" ✅ Decrypt RSS: {result['count']} articles fetched") | |
| for article in result['data'][:2]: | |
| print(f" • {article['title'][:60]}...") | |
| else: | |
| print(f" ❌ Decrypt RSS: {result.get('error')}") | |
| asyncio.run(test_apis()) | |
| print("\n" + "="*70) | |
| print("✅ New Providers Registry is working!") | |
| print("="*70) | |