Update latest changes

parent 2d7b817a
...@@ -49,10 +49,24 @@ else ...@@ -49,10 +49,24 @@ else
pip install -r requirements.txt --break-system-packages pip install -r requirements.txt --break-system-packages
fi 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 # Run the build script
echo "🔨 Starting build process..." echo "🔨 Starting build process..."
python3 build.py 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 # Removed Qt platform plugins and qt.conf copying for self-contained binary
echo "📦 Building self-contained binary - no external Qt files needed" echo "📦 Building self-contained binary - no external Qt files needed"
......
...@@ -21,7 +21,7 @@ if hasattr(sys, '_MEIPASS'): ...@@ -21,7 +21,7 @@ if hasattr(sys, '_MEIPASS'):
# Running in PyInstaller bundle # Running in PyInstaller bundle
qt_plugins_path = os.path.join(sys._MEIPASS, 'platforms') qt_plugins_path = os.path.join(sys._MEIPASS, 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = qt_plugins_path 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.core.application import MbetterClientApplication
from mbetterclient.utils.logger import setup_logging from mbetterclient.utils.logger import setup_logging
...@@ -203,6 +203,13 @@ Examples: ...@@ -203,6 +203,13 @@ Examples:
help='Configure timer delay in minutes for START_GAME_DELAYED message when START_GAME is received (default: 4 minutes)' 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() return parser.parse_args()
def validate_arguments(args): def validate_arguments(args):
...@@ -273,6 +280,13 @@ def main(): ...@@ -273,6 +280,13 @@ def main():
if args.db_path: if args.db_path:
settings.database_path = 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 # Create and initialize application
app = MbetterClientApplication(settings, start_timer=args.start_timer) app = MbetterClientApplication(settings, start_timer=args.start_timer)
......
...@@ -434,6 +434,9 @@ class APIClient(ThreadedComponent): ...@@ -434,6 +434,9 @@ class APIClient(ThreadedComponent):
self.config_manager = config_manager self.config_manager = config_manager
self.settings = settings 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 # HTTP session with retry logic
self.session = requests.Session() self.session = requests.Session()
self._setup_session() self._setup_session()
...@@ -547,16 +550,40 @@ class APIClient(ThreadedComponent): ...@@ -547,16 +550,40 @@ class APIClient(ThreadedComponent):
def _get_default_endpoints(self) -> Dict[str, Dict[str, Any]]: def _get_default_endpoints(self) -> Dict[str, Dict[str, Any]]:
"""Get default API endpoints configuration""" """Get default API endpoints configuration"""
# Get FastAPI server URL, token, and interval from 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_token = ""
api_interval = 1800 # 30 minutes default api_interval = 1800 # 30 minutes default
try: try:
api_config = self.config_manager.get_section_config("api") or {} api_config = self.config_manager.get_section_config("api") or {}
fastapi_url = api_config.get("fastapi_url", fastapi_url) logger.debug("RAW API CONFIG: {}".format(api_config))
api_token = api_config.get("api_token", api_token)
# 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) 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: 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 # Prepare authentication if token is provided
auth_config = None auth_config = None
...@@ -568,21 +595,26 @@ class APIClient(ThreadedComponent): ...@@ -568,21 +595,26 @@ class APIClient(ThreadedComponent):
"type": "bearer", "type": "bearer",
"token": api_token.strip() "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 # When token is provided: use configurable interval, 10 retries every 30 seconds
interval = api_interval interval = api_interval
retry_attempts = 10 retry_attempts = 10
retry_delay = 30 # 30 seconds retry_delay = 30 # 30 seconds
enabled = True enabled = True
else: else:
auth_config = None
logger.debug("AUTH CONFIG - No token available")
# When no token: use configurable interval or default, fewer retries, disabled by default # 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 interval = api_interval if api_interval != 1800 else 600 # Use configured or 10 minutes default
retry_attempts = 3 retry_attempts = 3
retry_delay = 5 retry_delay = 5
enabled = False # Disabled when no authentication 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": { "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 "method": "POST", # Use POST for /api/updates endpoint
"headers": headers, "headers": headers,
"auth": auth_config, "auth": auth_config,
...@@ -593,53 +625,12 @@ class APIClient(ThreadedComponent): ...@@ -593,53 +625,12 @@ class APIClient(ThreadedComponent):
"retry_delay": retry_delay, "retry_delay": retry_delay,
"response_handler": "updates" # Use updates handler for match synchronization "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): def run(self):
"""Main run loop""" """Main run loop"""
try: try:
...@@ -737,7 +728,7 @@ class APIClient(ThreadedComponent): ...@@ -737,7 +728,7 @@ class APIClient(ThreadedComponent):
endpoint.total_requests += 1 endpoint.total_requests += 1
self.stats['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 # Prepare request parameters
request_kwargs = { request_kwargs = {
...@@ -750,19 +741,24 @@ class APIClient(ThreadedComponent): ...@@ -750,19 +741,24 @@ class APIClient(ThreadedComponent):
# Prepare data/params based on method # Prepare data/params based on method
request_data = endpoint.params.copy() if endpoint.method == 'GET' else endpoint.data.copy() 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(): if endpoint.name == 'fastapi_main' and 'updates' in endpoint.url.lower():
last_timestamp = self._get_last_fixture_timestamp() last_timestamp = self._get_last_fixture_timestamp()
if last_timestamp: if last_timestamp:
if endpoint.method == 'GET':
request_data['from'] = last_timestamp
else:
request_data['from'] = last_timestamp request_data['from'] = last_timestamp
logger.debug("Adding 'from' parameter to {} request: {}".format(endpoint.name, last_timestamp))
logger.debug(f"Adding 'from' parameter to {endpoint.name} request: {last_timestamp}")
else: else:
# When no fixtures exist, send empty request to get all data # 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': if endpoint.method == 'GET':
request_kwargs['params'] = request_data request_kwargs['params'] = request_data
...@@ -782,10 +778,21 @@ class APIClient(ThreadedComponent): ...@@ -782,10 +778,21 @@ class APIClient(ThreadedComponent):
# Add authentication if configured # Add authentication if configured
if endpoint.auth: 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': 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': elif endpoint.auth.get('type') == 'basic':
request_kwargs['auth'] = (endpoint.auth.get('username'), endpoint.auth.get('password')) 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) # 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) curl_cmd = self._generate_curl_command(endpoint.method, endpoint.url, request_kwargs['headers'], request_data)
......
...@@ -120,6 +120,9 @@ class MbetterClientApplication: ...@@ -120,6 +120,9 @@ class MbetterClientApplication:
# Update settings from database # Update settings from database
stored_settings = self.config_manager.get_settings() stored_settings = self.config_manager.get_settings()
if stored_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) # Merge runtime settings with stored settings (command line overrides database)
stored_settings.fullscreen = self.settings.fullscreen stored_settings.fullscreen = self.settings.fullscreen
stored_settings.web_host = self.settings.web_host stored_settings.web_host = self.settings.web_host
...@@ -140,6 +143,11 @@ class MbetterClientApplication: ...@@ -140,6 +143,11 @@ class MbetterClientApplication:
self.settings = stored_settings 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 # Re-sync runtime settings to component configs
self.settings.qt.fullscreen = self.settings.fullscreen self.settings.qt.fullscreen = self.settings.fullscreen
self.settings.web.host = self.settings.web_host self.settings.web.host = self.settings.web_host
......
...@@ -310,6 +310,18 @@ class OverlayWebView(QWebEngineView): ...@@ -310,6 +310,18 @@ class OverlayWebView(QWebEngineView):
def _setup_custom_scheme(self): def _setup_custom_scheme(self):
"""Setup custom URL scheme handler for overlay resources""" """Setup custom URL scheme handler for overlay resources"""
try: 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 # Get the page's profile
profile = self.page().profile() profile = self.page().profile()
......
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