diff --git a/App.py b/App.py index 3ce6c53..e1206c4 100644 --- a/App.py +++ b/App.py @@ -367,12 +367,25 @@ def update_metrics_job(force=False): metrics = dashboard_service.fetch_metrics() if metrics: logging.info("Fetched metrics successfully") + + # Add config_reset flag to metrics from the config + config = load_config() + metrics["config_reset"] = config.get("config_reset", False) + logging.info(f"Added config_reset flag to metrics: {metrics.get('config_reset')}") # First check for notifications by comparing new metrics with old cached metrics notification_service.check_and_generate_notifications(metrics, cached_metrics) # Then update cached metrics after comparison cached_metrics = metrics + + # Clear the config_reset flag after it's been used + if metrics.get("config_reset"): + config = load_config() + if "config_reset" in config: + del config["config_reset"] + save_config(config) + logging.info("Cleared config_reset flag from configuration after use") # Update state history (only once) state_manager.update_metrics_history(metrics) @@ -771,7 +784,7 @@ def get_config(): @app.route("/api/config", methods=["POST"]) def update_config(): """API endpoint to update configuration.""" - global dashboard_service, worker_service # Add this to access the global dashboard_service + global dashboard_service, worker_service try: # Get the request data @@ -783,11 +796,19 @@ def update_config(): logging.error("Invalid configuration format") return jsonify({"error": "Invalid configuration format"}), 400 + # Get current config to check if currency is changing + current_config = load_config() + currency_changed = ( + "currency" in new_config and + new_config.get("currency") != current_config.get("currency", "USD") + ) + # Required fields and default values defaults = { "wallet": "yourwallethere", "power_cost": 0.0, - "power_usage": 0.0 + "power_usage": 0.0, + "currency": "USD" # Add default currency } # Merge new config with defaults for any missing fields @@ -802,7 +823,8 @@ def update_config(): dashboard_service = MiningDashboardService( new_config.get("power_cost", 0.0), new_config.get("power_usage", 0.0), - new_config.get("wallet") + new_config.get("wallet"), + network_fee=new_config.get("network_fee", 0.0) # Include network fee ) logging.info(f"Dashboard service reinitialized with new wallet: {new_config.get('wallet')}") @@ -810,6 +832,15 @@ def update_config(): worker_service.set_dashboard_service(dashboard_service) logging.info(f"Worker service updated with the new dashboard service") + # If currency changed, update notifications to use the new currency + if currency_changed: + try: + logging.info(f"Currency changed from {current_config.get('currency', 'USD')} to {new_config['currency']}") + updated_count = notification_service.update_notification_currency() + logging.info(f"Updated {updated_count} notifications to use {new_config['currency']} currency") + except Exception as e: + logging.error(f"Error updating notification currency: {e}") + # Force a metrics update to reflect the new configuration update_metrics_job(force=True) logging.info("Forced metrics update after configuration change") diff --git a/data_service.py b/data_service.py index 9ff6f46..e5df4fc 100644 --- a/data_service.py +++ b/data_service.py @@ -164,6 +164,18 @@ class MiningDashboardService: metrics["server_timestamp"] = datetime.now(ZoneInfo(get_timezone())).isoformat() metrics["server_start_time"] = datetime.now(ZoneInfo(get_timezone())).isoformat() + # Get the configured currency + from config import load_config + config = load_config() + selected_currency = config.get("currency", "USD") + + # Fetch exchange rates + exchange_rates = self.fetch_exchange_rates() + + # Add to metrics + metrics["currency"] = selected_currency + metrics["exchange_rates"] = exchange_rates + # Log execution time execution_time = time.time() - start_time metrics["execution_time"] = execution_time @@ -452,6 +464,32 @@ class MiningDashboardService: logging.error(f"Error fetching {url}: {e}") return None + # Add the fetch_exchange_rates method after the fetch_url method + def fetch_exchange_rates(self, base_currency="USD"): + """ + Fetch currency exchange rates from a public API. + + Args: + base_currency (str): Base currency for rates (default: USD) + + Returns: + dict: Exchange rates for supported currencies + """ + try: + # Use exchangerate-api for currency rates + url = f"https://api.exchangerate-api.com/v4/latest/{base_currency}" + response = self.session.get(url, timeout=5) + + if response.ok: + data = response.json() + return data.get('rates', {}) + else: + logging.error(f"Failed to fetch exchange rates: {response.status_code}") + return {} + except Exception as e: + logging.error(f"Error fetching exchange rates: {e}") + return {} + def get_bitcoin_stats(self): """ Fetch Bitcoin network statistics with improved error handling and caching. diff --git a/docker-compose.yml b/docker-compose.yml index cb75a02..899ea98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,6 @@ services: - "5000:5000" environment: - REDIS_URL=redis://redis:6379 - - WALLET=35eS5Lsqw8NCjFJ8zhp9JaEmyvLDwg6XtS - - POWER_COST=0 - - POWER_USAGE=0 - - NETWORK_FEE=0 - - TIMEZONE=America/Los_Angeles - LOG_LEVEL=INFO volumes: - ./logs:/app/logs diff --git a/notification_service.py b/notification_service.py index fe73a7e..f4a0b06 100644 --- a/notification_service.py +++ b/notification_service.py @@ -1,14 +1,17 @@ -# notification_service.py +# notification_service.py import logging import json import time import uuid import pytz +import re from datetime import datetime, timedelta from enum import Enum from collections import deque from typing import List, Dict, Any, Optional, Union -from config import get_timezone +from config import get_timezone, load_config + +from data_service import MiningDashboardService # Constants to replace magic values ONE_DAY_SECONDS = 86400 @@ -29,6 +32,52 @@ class NotificationCategory(Enum): EARNINGS = "earnings" SYSTEM = "system" +# Currency utility functions +def get_currency_symbol(currency): + """Return symbol for the specified currency""" + symbols = { + 'USD': '$', + 'EUR': '€', + 'GBP': '£', + 'JPY': '¥', + 'CAD': 'CA$', + 'AUD': 'A$', + 'CNY': '¥', + 'KRW': '₩', + 'BRL': 'R$', + 'CHF': 'Fr' + } + return symbols.get(currency, '$') + +def format_currency_value(value, currency, exchange_rates): + """Format a USD value in the selected currency""" + if value is None or value == "N/A": + return "N/A" + + # Get exchange rate (default to 1.0 if not found) + exchange_rate = exchange_rates.get(currency, 1.0) + converted_value = value * exchange_rate + + # Get currency symbol + symbol = get_currency_symbol(currency) + + # Format with or without decimals based on currency + if currency in ['JPY', 'KRW']: + return f"{symbol}{int(converted_value):,}" + else: + return f"{symbol}{converted_value:.2f}" + +def get_exchange_rates(): + """Get exchange rates with caching""" + try: + # Create a dashboard service instance for fetching exchange rates + dashboard_service = MiningDashboardService(0, 0, "", 0) + exchange_rates = dashboard_service.fetch_exchange_rates() + return exchange_rates + except Exception as e: + logging.error(f"Error fetching exchange rates for notifications: {e}") + return {} # Return empty dict if failed + class NotificationService: """Service for managing mining dashboard notifications.""" @@ -312,6 +361,11 @@ class NotificationService: if not current_metrics: logging.warning("[NotificationService] No current metrics available, skipping notification checks") return new_notifications + + # Skip notification generation after configuration reset + if current_metrics.get("wallet") == "yourwallethere" or current_metrics.get("config_reset", False): + logging.info("[NotificationService] Configuration reset detected, skipping all notifications") + return new_notifications # Check for block updates (using persistent storage) last_block_height = current_metrics.get("last_block_height") @@ -384,12 +438,22 @@ class NotificationService: hashrate_24hr = metrics.get("hashrate_24hr", 0) hashrate_unit = metrics.get("hashrate_24hr_unit", "TH/s") - # Format daily earnings + # Format daily earnings with user's currency daily_mined_sats = metrics.get("daily_mined_sats", 0) daily_profit_usd = metrics.get("daily_profit_usd", 0) + + # Get user's currency preference + config = load_config() + user_currency = config.get("currency", "USD") + + # Get exchange rates + exchange_rates = get_exchange_rates() + + # Format with the user's currency + formatted_profit = format_currency_value(daily_profit_usd, user_currency, exchange_rates) # Build message - message = f"Daily Mining Summary: {hashrate_24hr} {hashrate_unit} average hashrate, {daily_mined_sats} SATS mined (${daily_profit_usd:.2f})" + message = f"Daily Mining Summary: {hashrate_24hr} {hashrate_unit} average hashrate, {daily_mined_sats} SATS mined ({formatted_profit})" # Add notification logging.info(f"[NotificationService] Generating daily stats notification: {message}") @@ -401,7 +465,8 @@ class NotificationService: "hashrate": hashrate_24hr, "unit": hashrate_unit, "daily_sats": daily_mined_sats, - "daily_profit": daily_profit_usd + "daily_profit": daily_profit_usd, + "currency": user_currency } ) except Exception as e: @@ -551,8 +616,20 @@ class NotificationService: def _check_earnings_progress(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Check for significant earnings progress or payout approach.""" try: + # First check for configuration reset via Alt+W (this is a more robust check) + # This specifically looks for the default "yourwallethere" wallet which indicates Alt+W was used + current_wallet = str(current.get("wallet", "")) + if current_wallet == "yourwallethere": + logging.info("[NotificationService] Detected wallet reset to default (likely Alt+W) - skipping payout notification") + return None + + # Check if ANY config value changed that might affect balance + if self._is_configuration_change_affecting_balance(current, previous): + logging.info("[NotificationService] Configuration change detected - skipping payout notification") + return None + current_unpaid = self._parse_numeric_value(current.get("unpaid_earnings", "0")) - + # Check if approaching payout if current.get("est_time_to_payout"): est_time = current.get("est_time_to_payout") @@ -605,9 +682,27 @@ class NotificationService: logging.error(f"[NotificationService] Error checking earnings progress: {e}") return None + def _is_configuration_change_affecting_balance(self, current: Dict[str, Any], previous: Dict[str, Any]) -> bool: + """Check if any configuration changed that would affect balance calculations.""" + # Check wallet + if "wallet" in current and "wallet" in previous: + if current.get("wallet") != previous.get("wallet"): + return True + + # Check currency + if "currency" in current and "currency" in previous: + if current.get("currency") != previous.get("currency"): + return True + + # Check for emergency reset flag + if current.get("config_reset", False): + return True + + return False + def _should_send_payout_notification(self) -> bool: """Check if enough time has passed since the last payout notification.""" if self.last_payout_notification_time is None: return True time_since_last_notification = self._get_current_time() - self.last_payout_notification_time - return time_since_last_notification.total_seconds() > ONE_DAY_SECONDS \ No newline at end of file + return time_since_last_notification.total_seconds() > ONE_DAY_SECONDS diff --git a/static/js/BitcoinProgressBar.js b/static/js/BitcoinProgressBar.js index f105350..13d4b14 100644 --- a/static/js/BitcoinProgressBar.js +++ b/static/js/BitcoinProgressBar.js @@ -397,8 +397,12 @@ const BitcoinMinuteRefresh = (function () {