Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterc
Commits
e9ca7272
Commit
e9ca7272
authored
Sep 26, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update latest changes
parent
2d7b817a
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
123 additions
and
68 deletions
+123
-68
build.sh
build.sh
+14
-0
main.py
main.py
+16
-2
client.py
mbetterclient/api_client/client.py
+68
-61
application.py
mbetterclient/core/application.py
+10
-2
player.py
mbetterclient/qt_player/player.py
+15
-3
No files found.
build.sh
View file @
e9ca7272
...
...
@@ -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"
...
...
main.py
View file @
e9ca7272
...
...
@@ -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
)
...
...
mbetterclient/api_client/client.py
View file @
e9ca7272
...
...
@@ -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
)
...
...
mbetterclient/core/application.py
View file @
e9ca7272
...
...
@@ -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
...
...
mbetterclient/qt_player/player.py
View file @
e9ca7272
...
...
@@ -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}"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment