Update latest changes

parent 2d7b817a
......@@ -49,10 +49,24 @@ else
pip install -r requirements.txt --break-system-packages
fi
# Backup MBetterDiscovery.exe if it exists
if [ -f "dist/MBetterDiscovery.exe" ]; then
echo "📦 Backing up MBetterDiscovery.exe..."
cp dist/MBetterDiscovery.exe /tmp/MBetterDiscovery.exe.backup
fi
# Run the build script
echo "🔨 Starting build process..."
python3 build.py
# Restore MBetterDiscovery.exe if backup exists
if [ -f "/tmp/MBetterDiscovery.exe.backup" ]; then
echo "📦 Restoring MBetterDiscovery.exe..."
mkdir -p dist
cp /tmp/MBetterDiscovery.exe.backup dist/MBetterDiscovery.exe
rm /tmp/MBetterDiscovery.exe.backup
fi
# Removed Qt platform plugins and qt.conf copying for self-contained binary
echo "📦 Building self-contained binary - no external Qt files needed"
......
......@@ -21,7 +21,7 @@ if hasattr(sys, '_MEIPASS'):
# Running in PyInstaller bundle
qt_plugins_path = os.path.join(sys._MEIPASS, 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = qt_plugins_path
print(f"Set QT_QPA_PLATFORM_PLUGIN_PATH to: {qt_plugins_path}")
print("Set QT_QPA_PLATFORM_PLUGIN_PATH to: {}".format(qt_plugins_path))
from mbetterclient.core.application import MbetterClientApplication
from mbetterclient.utils.logger import setup_logging
......@@ -203,6 +203,13 @@ Examples:
help='Configure timer delay in minutes for START_GAME_DELAYED message when START_GAME is received (default: 4 minutes)'
)
parser.add_argument(
'--rustdesk_id',
type=str,
default=None,
help='RustDesk ID for periodic whoami API calls (enables /api/whoami endpoint)'
)
return parser.parse_args()
def validate_arguments(args):
......@@ -272,7 +279,14 @@ def main():
if args.db_path:
settings.database_path = args.db_path
# Set RustDesk ID if provided
if args.rustdesk_id:
settings.api.rustdesk_id = args.rustdesk_id
logger.info("COMMAND LINE: Set rustdesk_id to: {}".format(args.rustdesk_id))
else:
logger.info("COMMAND LINE: No rustdesk_id provided")
# Create and initialize application
app = MbetterClientApplication(settings, start_timer=args.start_timer)
......
......@@ -433,6 +433,9 @@ class APIClient(ThreadedComponent):
self.db_manager = db_manager
self.config_manager = config_manager
self.settings = settings
logger.debug("API CLIENT INIT: rustdesk_id from settings: '{}' (type: {})".format(
self.settings.rustdesk_id, type(self.settings.rustdesk_id)))
# HTTP session with retry logic
self.session = requests.Session()
......@@ -547,16 +550,40 @@ class APIClient(ThreadedComponent):
def _get_default_endpoints(self) -> Dict[str, Dict[str, Any]]:
"""Get default API endpoints configuration"""
# Get FastAPI server URL, token, and interval from configuration
fastapi_url = "https://mbetter.nexlab.net/api/updates"
fastapi_url = "https://mbetter.nexlab.net"
api_token = ""
api_interval = 1800 # 30 minutes default
try:
api_config = self.config_manager.get_section_config("api") or {}
fastapi_url = api_config.get("fastapi_url", fastapi_url)
api_token = api_config.get("api_token", api_token)
logger.debug("RAW API CONFIG: {}".format(api_config))
# Load fastapi_url
config_fastapi_url = api_config.get("fastapi_url")
logger.debug("CONFIG FASTAPI_URL: '{}' (type: {})".format(config_fastapi_url, type(config_fastapi_url)))
if config_fastapi_url and config_fastapi_url.strip():
fastapi_url = config_fastapi_url.strip()
logger.debug("USING CONFIG URL: {}".format(fastapi_url))
else:
logger.debug("CONFIG URL EMPTY, USING DEFAULT: {}".format(fastapi_url))
# Load api_token
config_api_token = api_config.get("api_token")
logger.debug("CONFIG API_TOKEN: '{}' (type: {}, length: {})".format(
config_api_token, type(config_api_token), len(config_api_token) if config_api_token else 0))
if config_api_token and config_api_token.strip():
api_token = config_api_token.strip()
logger.debug("USING CONFIG TOKEN: {}...{}".format(api_token[:10], api_token[-10:] if len(api_token) > 20 else api_token))
else:
logger.debug("CONFIG TOKEN EMPTY, USING DEFAULT: {}".format(api_token))
api_interval = api_config.get("api_interval", api_interval)
logger.debug("API CONFIG LOADED - fastapi_url: {}, api_token: {}, api_interval: {}".format(
fastapi_url, '*' * len(api_token) if api_token else 'empty', api_interval))
except Exception as e:
logger.warning(f"Could not load API configuration, using defaults: {e}")
logger.warning("Could not load API configuration, using defaults: {}".format(e))
logger.debug("CONFIG LOAD ERROR DETAILS: {}".format(str(e)))
# Prepare authentication if token is provided
auth_config = None
......@@ -568,21 +595,26 @@ class APIClient(ThreadedComponent):
"type": "bearer",
"token": api_token.strip()
}
logger.debug("AUTH CONFIG - Token available, length: {}".format(len(api_token.strip())))
# When token is provided: use configurable interval, 10 retries every 30 seconds
interval = api_interval
retry_attempts = 10
retry_delay = 30 # 30 seconds
enabled = True
else:
auth_config = None
logger.debug("AUTH CONFIG - No token available")
# When no token: use configurable interval or default, fewer retries, disabled by default
interval = api_interval if api_interval != 1800 else 600 # Use configured or 10 minutes default
retry_attempts = 3
retry_delay = 5
enabled = False # Disabled when no authentication
return {
# Construct full URLs by appending paths to base URL
fastapi_main_url = fastapi_url.rstrip('/') + "/api/updates"
endpoints = {
"fastapi_main": {
"url": fastapi_url, # Use the exact URL provided, don't modify it
"url": fastapi_main_url,
"method": "POST", # Use POST for /api/updates endpoint
"headers": headers,
"auth": auth_config,
......@@ -593,52 +625,11 @@ class APIClient(ThreadedComponent):
"retry_delay": retry_delay,
"response_handler": "updates" # Use updates handler for match synchronization
},
"news_example": {
"url": "https://newsapi.org/v2/top-headlines",
"method": "GET",
"headers": {
"X-API-Key": "your-api-key-here"
},
"params": {
"country": "us",
"pageSize": 5
},
"interval": 300, # 5 minutes
"enabled": False, # Disabled by default until API key is configured
"timeout": 30,
"retry_attempts": 3,
"retry_delay": 5,
"response_handler": "news"
},
"sports_example": {
"url": "https://api.sportsdata.io/v3/nfl/scores/json/LiveBoxScores",
"method": "GET",
"headers": {
"Ocp-Apim-Subscription-Key": "your-api-key-here"
},
"interval": 60, # 1 minute during games
"enabled": False,
"timeout": 30,
"retry_attempts": 3,
"retry_delay": 5,
"response_handler": "sports"
},
"weather_example": {
"url": "https://api.openweathermap.org/data/2.5/weather",
"method": "GET",
"params": {
"q": "New York",
"appid": "your-api-key-here",
"units": "metric"
},
"interval": 1800, # 30 minutes
"enabled": False,
"timeout": 15,
"retry_attempts": 2,
"retry_delay": 3,
"response_handler": "default"
}
}
logger.debug("ENDPOINT URLS - base_url: {}, fastapi_main: {}".format(fastapi_url, fastapi_main_url))
return endpoints
def run(self):
"""Main run loop"""
......@@ -737,7 +728,7 @@ class APIClient(ThreadedComponent):
endpoint.total_requests += 1
self.stats['total_requests'] += 1
logger.debug(f"Executing API request: {endpoint.name} -> {endpoint.url}")
logger.debug("Executing API request: {} -> {}".format(endpoint.name, endpoint.url))
# Prepare request parameters
request_kwargs = {
......@@ -750,19 +741,24 @@ class APIClient(ThreadedComponent):
# Prepare data/params based on method
request_data = endpoint.params.copy() if endpoint.method == 'GET' else endpoint.data.copy()
# For FastAPI /api/updates endpoint, add 'from' parameter if we have fixtures
# For FastAPI /api/updates endpoint, add 'from' parameter and rustdesk_id if provided
if endpoint.name == 'fastapi_main' and 'updates' in endpoint.url.lower():
last_timestamp = self._get_last_fixture_timestamp()
if last_timestamp:
if endpoint.method == 'GET':
request_data['from'] = last_timestamp
else:
request_data['from'] = last_timestamp
logger.debug(f"Adding 'from' parameter to {endpoint.name} request: {last_timestamp}")
request_data['from'] = last_timestamp
logger.debug("Adding 'from' parameter to {} request: {}".format(endpoint.name, last_timestamp))
else:
# When no fixtures exist, send empty request to get all data
logger.debug(f"No fixtures found, sending empty request to {endpoint.name}")
logger.debug("No fixtures found, sending empty request to {}".format(endpoint.name))
# Add rustdesk_id to the updates call if provided
logger.debug("DEBUG: Checking rustdesk_id - value: '{}' (type: {}), bool: {}".format(
self.settings.rustdesk_id, type(self.settings.rustdesk_id), bool(self.settings.rustdesk_id)))
if self.settings.rustdesk_id:
request_data['rustdesk_id'] = self.settings.rustdesk_id
logger.debug("Adding rustdesk_id to {} request: {}".format(endpoint.name, self.settings.rustdesk_id))
else:
logger.debug("rustdesk_id not set, skipping addition to request")
if endpoint.method == 'GET':
request_kwargs['params'] = request_data
......@@ -782,10 +778,21 @@ class APIClient(ThreadedComponent):
# Add authentication if configured
if endpoint.auth:
logger.debug("AUTH DEBUG - endpoint.auth: {}".format(endpoint.auth))
logger.debug("AUTH DEBUG - auth type: {}".format(endpoint.auth.get('type')))
if endpoint.auth.get('type') == 'bearer':
request_kwargs['headers']['Authorization'] = f"Bearer {endpoint.auth.get('token')}"
token = endpoint.auth.get('token')
logger.debug("AUTH DEBUG - raw token value: '{}' (repr: {})".format(token, repr(token)))
if token and token.strip():
request_kwargs['headers']['Authorization'] = "Bearer {}".format(token.strip())
logger.debug("AUTH - Added Bearer token for {}: {}...{}".format(endpoint.name, token.strip()[:10], token.strip()[-10:] if len(token.strip()) > 20 else token.strip()))
else:
logger.debug("AUTH - Bearer token configured but empty for {}".format(endpoint.name))
elif endpoint.auth.get('type') == 'basic':
request_kwargs['auth'] = (endpoint.auth.get('username'), endpoint.auth.get('password'))
logger.debug("AUTH - Added Basic auth for {}".format(endpoint.name))
else:
logger.debug("AUTH - No authentication configured for {}".format(endpoint.name))
# Generate curl command for easy debugging/replication (after auth is added)
curl_cmd = self._generate_curl_command(endpoint.method, endpoint.url, request_kwargs['headers'], request_data)
......
......@@ -120,6 +120,9 @@ class MbetterClientApplication:
# Update settings from database
stored_settings = self.config_manager.get_settings()
if stored_settings:
# Preserve command-line rustdesk_id before merging
command_line_rustdesk_id = getattr(self.settings.api, 'rustdesk_id', None)
# Merge runtime settings with stored settings (command line overrides database)
stored_settings.fullscreen = self.settings.fullscreen
stored_settings.web_host = self.settings.web_host
......@@ -131,14 +134,19 @@ class MbetterClientApplication:
# Preserve command line Qt overlay setting
stored_settings.qt.use_native_overlay = self.settings.qt.use_native_overlay
# Preserve command line SSL settings
stored_settings.web.enable_ssl = self.settings.web.enable_ssl
stored_settings.web.ssl_cert_path = self.settings.web.ssl_cert_path
stored_settings.web.ssl_key_path = self.settings.web.ssl_key_path
stored_settings.web.ssl_auto_generate = self.settings.web.ssl_auto_generate
self.settings = stored_settings
# Restore command-line rustdesk_id after settings merge
if command_line_rustdesk_id is not None:
self.settings.api.rustdesk_id = command_line_rustdesk_id
logger.debug("APPLICATION: Restored command-line rustdesk_id: {}".format(command_line_rustdesk_id))
# Re-sync runtime settings to component configs
self.settings.qt.fullscreen = self.settings.fullscreen
......
......@@ -310,15 +310,27 @@ class OverlayWebView(QWebEngineView):
def _setup_custom_scheme(self):
"""Setup custom URL scheme handler for overlay resources"""
try:
# Register the custom scheme before installing the handler
from PyQt6.QtWebEngineCore import QWebEngineUrlScheme
overlay_scheme = QWebEngineUrlScheme(b"overlay")
overlay_scheme.setSyntax(QWebEngineUrlScheme.Syntax.HostAndPort)
overlay_scheme.setDefaultPort(0)
overlay_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
QWebEngineUrlScheme.Flag.LocalScheme |
QWebEngineUrlScheme.Flag.LocalAccessAllowed)
QWebEngineUrlScheme.registerScheme(overlay_scheme)
logger.info("Custom overlay:// URL scheme registered successfully")
# Get the page's profile
profile = self.page().profile()
# Create and install URL scheme handler
self.scheme_handler = OverlayUrlSchemeHandler(self)
profile.installUrlSchemeHandler(b"overlay", self.scheme_handler)
logger.info("Custom overlay:// URL scheme handler installed successfully")
except Exception as e:
logger.error(f"Failed to setup custom URL scheme: {e}")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment