mc5-api-client 1.0.15__py3-none-any.whl → 1.0.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mc5_api_client/__init__.py +107 -2
- mc5_api_client/account.py +352 -0
- mc5_api_client/account_quick.py +246 -0
- mc5_api_client/alerts.py +336 -0
- mc5_api_client/alerts_quick.py +210 -0
- mc5_api_client/cli.py +1 -1
- mc5_api_client/client.py +875 -34
- mc5_api_client/debug.py +259 -0
- mc5_api_client/federation.py +257 -0
- mc5_api_client/federation_quick.py +198 -0
- mc5_api_client/platform.py +109 -0
- mc5_api_client/simple_client.py +563 -19
- mc5_api_client/squad_battle.py +439 -0
- mc5_api_client/squad_battle_quick.py +223 -0
- mc5_api_client/telemetry.py +344 -0
- mc5_api_client/transfer.py +348 -0
- mc5_api_client/transfer_quick.py +280 -0
- {mc5_api_client-1.0.15.dist-info → mc5_api_client-1.0.17.dist-info}/METADATA +581 -7
- mc5_api_client-1.0.17.dist-info/RECORD +28 -0
- mc5_api_client-1.0.15.dist-info/RECORD +0 -15
- {mc5_api_client-1.0.15.dist-info → mc5_api_client-1.0.17.dist-info}/WHEEL +0 -0
- {mc5_api_client-1.0.15.dist-info → mc5_api_client-1.0.17.dist-info}/entry_points.txt +0 -0
- {mc5_api_client-1.0.15.dist-info → mc5_api_client-1.0.17.dist-info}/licenses/LICENSE +0 -0
- {mc5_api_client-1.0.15.dist-info → mc5_api_client-1.0.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
3
|
+
# | Email : chizoba2026@hotmail.com
|
|
4
|
+
# | File : account_quick.py
|
|
5
|
+
# | License | MIT License © 2026 Chizoba
|
|
6
|
+
# | Brief | Quick functions for account management
|
|
7
|
+
# ────────────────★─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
from typing import Optional, Dict, Any
|
|
10
|
+
from .simple_client import SimpleMC5Client
|
|
11
|
+
from .telemetry import report_usage
|
|
12
|
+
from .debug import debug_function, debug_print
|
|
13
|
+
|
|
14
|
+
@debug_function
|
|
15
|
+
def quick_get_account_info(username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
16
|
+
"""
|
|
17
|
+
Quick function to get comprehensive account information.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
username: MC5 username
|
|
21
|
+
password: MC5 password
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Complete account information with analysis or None if failed
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
with SimpleMC5Client(username, password) as client:
|
|
28
|
+
if client.connect():
|
|
29
|
+
account_info = client.client.get_account_info()
|
|
30
|
+
|
|
31
|
+
# Display summary
|
|
32
|
+
analysis = account_info.get('analysis', {})
|
|
33
|
+
summary = analysis.get('account_summary', {})
|
|
34
|
+
|
|
35
|
+
debug_print(f"✅ Account info retrieved", "success")
|
|
36
|
+
debug_print(f"👤 Account ID: {summary.get('account_id', 'Unknown')}", "info")
|
|
37
|
+
debug_print(f"🔐 Credentials: {summary.get('total_credentials', 0)}", "info")
|
|
38
|
+
debug_print(f"📱 Installations: {summary.get('total_installations', 0)}", "info")
|
|
39
|
+
debug_print(f"🏷️ Aliases: {summary.get('alias_count', 0)}", "info")
|
|
40
|
+
debug_print(f"👻 Ghost Account: {summary.get('is_ghost', False)}", "info")
|
|
41
|
+
|
|
42
|
+
return account_info
|
|
43
|
+
return None
|
|
44
|
+
except Exception as e:
|
|
45
|
+
debug_print(f"❌ Failed to get account info: {e}", "error")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
@debug_function
|
|
49
|
+
def quick_get_device_history(username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
50
|
+
"""
|
|
51
|
+
Quick function to get device installation history.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
username: MC5 username
|
|
55
|
+
password: MC5 password
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Device history with timeline or None if failed
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
with SimpleMC5Client(username, password) as client:
|
|
62
|
+
if client.connect():
|
|
63
|
+
device_history = client.client.get_device_history()
|
|
64
|
+
|
|
65
|
+
total_devices = device_history.get('total_unique_devices', 0)
|
|
66
|
+
debug_print(f"✅ Device history retrieved: {total_devices} unique devices", "success")
|
|
67
|
+
|
|
68
|
+
# Show recent devices
|
|
69
|
+
timeline = device_history.get('device_timeline', [])
|
|
70
|
+
for i, device in enumerate(timeline[-3:], 1): # Show last 3
|
|
71
|
+
model = device.get('model', 'Unknown')
|
|
72
|
+
country = device.get('country', 'Unknown')
|
|
73
|
+
debug_print(f" Device {i}: {model} ({country})", "info")
|
|
74
|
+
|
|
75
|
+
return device_history
|
|
76
|
+
return None
|
|
77
|
+
except Exception as e:
|
|
78
|
+
debug_print(f"❌ Failed to get device history: {e}", "error")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
@debug_function
|
|
82
|
+
def quick_get_credential_summary(username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
83
|
+
"""
|
|
84
|
+
Quick function to get credential summary.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
username: MC5 username
|
|
88
|
+
password: MC5 password
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Credential summary or None if failed
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
with SimpleMC5Client(username, password) as client:
|
|
95
|
+
if client.connect():
|
|
96
|
+
credential_summary = client.client.get_credential_summary()
|
|
97
|
+
|
|
98
|
+
credential_count = credential_summary.get('credential_count', 0)
|
|
99
|
+
debug_print(f"✅ Credential summary: {credential_count} credentials", "success")
|
|
100
|
+
|
|
101
|
+
# Show credential types
|
|
102
|
+
cred_types = credential_summary.get('credential_types', [])
|
|
103
|
+
for cred_type in cred_types:
|
|
104
|
+
debug_print(f" Type: {cred_type}", "info")
|
|
105
|
+
|
|
106
|
+
return credential_summary
|
|
107
|
+
return None
|
|
108
|
+
except Exception as e:
|
|
109
|
+
debug_print(f"❌ Failed to get credential summary: {e}", "error")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
@debug_function
|
|
113
|
+
def quick_analyze_account_security(username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""
|
|
115
|
+
Quick function to analyze account security.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
username: MC5 username
|
|
119
|
+
password: MC5 password
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Security analysis or None if failed
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
with SimpleMC5Client(username, password) as client:
|
|
126
|
+
if client.connect():
|
|
127
|
+
account_info = client.client.get_account_info()
|
|
128
|
+
security_analysis = account_info.get('analysis', {}).get('security', {})
|
|
129
|
+
|
|
130
|
+
security_score = security_analysis.get('security_score', 0)
|
|
131
|
+
risk_level = security_analysis.get('risk_level', 'Unknown')
|
|
132
|
+
issues = security_analysis.get('security_issues', [])
|
|
133
|
+
|
|
134
|
+
debug_print(f"✅ Security analysis completed", "success")
|
|
135
|
+
debug_print(f"🔒 Security Score: {security_score}/100", "info")
|
|
136
|
+
debug_print(f"⚠️ Risk Level: {risk_level}", "info")
|
|
137
|
+
|
|
138
|
+
if issues:
|
|
139
|
+
debug_print("🚨 Security Issues:", "warning")
|
|
140
|
+
for issue in issues:
|
|
141
|
+
debug_print(f" • {issue}", "warning")
|
|
142
|
+
else:
|
|
143
|
+
debug_print("✅ No security issues detected", "success")
|
|
144
|
+
|
|
145
|
+
return security_analysis
|
|
146
|
+
return None
|
|
147
|
+
except Exception as e:
|
|
148
|
+
debug_print(f"❌ Failed to analyze security: {e}", "error")
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
@debug_function
|
|
152
|
+
def quick_get_account_overview(username: str, password: str) -> Optional[Dict[str, Any]]:
|
|
153
|
+
"""
|
|
154
|
+
Quick function to get complete account overview.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
username: MC5 username
|
|
158
|
+
password: MC5 password
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Complete account overview or None if failed
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
with SimpleMC5Client(username, password) as client:
|
|
165
|
+
if client.connect():
|
|
166
|
+
# Get all account data
|
|
167
|
+
account_info = client.client.get_account_info()
|
|
168
|
+
device_history = client.client.get_device_history()
|
|
169
|
+
credential_summary = client.client.get_credential_summary()
|
|
170
|
+
|
|
171
|
+
# Create overview
|
|
172
|
+
analysis = account_info.get('analysis', {})
|
|
173
|
+
account_summary = analysis.get('account_summary', {})
|
|
174
|
+
installation_analysis = analysis.get('installations', {})
|
|
175
|
+
security_analysis = analysis.get('security', {})
|
|
176
|
+
|
|
177
|
+
overview = {
|
|
178
|
+
"account_summary": account_summary,
|
|
179
|
+
"device_summary": {
|
|
180
|
+
"total_devices": device_history.get('total_unique_devices', 0),
|
|
181
|
+
"most_common_model": installation_analysis.get('most_common_model'),
|
|
182
|
+
"most_common_country": installation_analysis.get('most_common_country')
|
|
183
|
+
},
|
|
184
|
+
"credential_summary": {
|
|
185
|
+
"total_credentials": credential_summary.get('credential_count', 0),
|
|
186
|
+
"credential_types": credential_summary.get('credential_types', [])
|
|
187
|
+
},
|
|
188
|
+
"security_summary": {
|
|
189
|
+
"security_score": security_analysis.get('security_score', 0),
|
|
190
|
+
"risk_level": security_analysis.get('risk_level', 'Unknown'),
|
|
191
|
+
"security_issues": security_analysis.get('security_issues', [])
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
debug_print(f"✅ Account overview completed", "success")
|
|
196
|
+
debug_print(f"👤 Account: {account_summary.get('account_id', 'Unknown')}", "info")
|
|
197
|
+
debug_print(f"📱 Devices: {overview['device_summary']['total_devices']}", "info")
|
|
198
|
+
debug_print(f"🔐 Credentials: {overview['credential_summary']['total_credentials']}", "info")
|
|
199
|
+
debug_print(f"🔒 Security: {overview['security_summary']['security_score']}/100 ({overview['security_summary']['risk_level']})", "info")
|
|
200
|
+
|
|
201
|
+
return overview
|
|
202
|
+
return None
|
|
203
|
+
except Exception as e:
|
|
204
|
+
debug_print(f"❌ Failed to get account overview: {e}", "error")
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
@debug_function
|
|
208
|
+
def quick_export_account_data(username: str, password: str, filename: str = "account_data.json") -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Quick function to export complete account data to file.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
username: MC5 username
|
|
214
|
+
password: MC5 password
|
|
215
|
+
filename: Output filename
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if successful, False otherwise
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
with SimpleMC5Client(username, password) as client:
|
|
222
|
+
if client.connect():
|
|
223
|
+
# Get complete account data
|
|
224
|
+
account_info = client.client.get_account_info()
|
|
225
|
+
device_history = client.client.get_device_history()
|
|
226
|
+
credential_summary = client.client.get_credential_summary()
|
|
227
|
+
|
|
228
|
+
# Combine all data
|
|
229
|
+
export_data = {
|
|
230
|
+
"export_timestamp": __import__('datetime').datetime.now().isoformat(),
|
|
231
|
+
"account_info": account_info,
|
|
232
|
+
"device_history": device_history,
|
|
233
|
+
"credential_summary": credential_summary
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Write to file
|
|
237
|
+
import json
|
|
238
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
239
|
+
json.dump(export_data, f, indent=2, ensure_ascii=False)
|
|
240
|
+
|
|
241
|
+
debug_print(f"✅ Account data exported to {filename}", "success")
|
|
242
|
+
return True
|
|
243
|
+
return False
|
|
244
|
+
except Exception as e:
|
|
245
|
+
debug_print(f"❌ Failed to export account data: {e}", "error")
|
|
246
|
+
return False
|
mc5_api_client/alerts.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
3
|
+
# | Email : chizoba2026@hotmail.com
|
|
4
|
+
# | File : alerts.py
|
|
5
|
+
# | License | MIT License © 2026 Chizoba
|
|
6
|
+
# | Brief | Real-time alerts and streaming functionality
|
|
7
|
+
# ────────────────★─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
import threading
|
|
12
|
+
from typing import Optional, Dict, Any, List, Callable
|
|
13
|
+
from .telemetry import report_error, report_usage
|
|
14
|
+
from .debug import debug_function, debug_print
|
|
15
|
+
|
|
16
|
+
class AlertsMixin:
|
|
17
|
+
"""Mixin class for real-time alerts and streaming functionality."""
|
|
18
|
+
|
|
19
|
+
@debug_function
|
|
20
|
+
def start_alert_stream(self,
|
|
21
|
+
webhook_url: Optional[str] = None,
|
|
22
|
+
alert_types: List[str] = None,
|
|
23
|
+
callback: Optional[Callable] = None) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Start real-time alert streaming for live notifications.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
webhook_url: Optional webhook URL for alert forwarding
|
|
29
|
+
alert_types: List of alert types to subscribe to
|
|
30
|
+
callback: Optional callback function for handling alerts
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Stream session information
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
if alert_types is None:
|
|
37
|
+
alert_types = ["connection", "message", "connection_request", "connection_request_accepted"]
|
|
38
|
+
|
|
39
|
+
# Get a fedex server endpoint (these appear to be load balanced)
|
|
40
|
+
fedex_servers = [
|
|
41
|
+
"eur-fedex-fsg006.gameloft.com:45040",
|
|
42
|
+
"eur-fedex-fsg002.gameloft.com:41245",
|
|
43
|
+
"eur-fedex-fsg001.gameloft.com:45040"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Use first available server
|
|
47
|
+
server = fedex_servers[0]
|
|
48
|
+
url = f"https://{server}/alerts/me"
|
|
49
|
+
|
|
50
|
+
params = {
|
|
51
|
+
"access_token": self._token_data["access_token"],
|
|
52
|
+
"content_type": "event-stream",
|
|
53
|
+
"push_method": "streaming",
|
|
54
|
+
"alert_types": ",".join(alert_types)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
debug_print(f"🌊 Starting alert stream from {server}", "info")
|
|
58
|
+
debug_print(f"📋 Alert types: {', '.join(alert_types)}", "info")
|
|
59
|
+
|
|
60
|
+
# Create stream session info
|
|
61
|
+
session_info = {
|
|
62
|
+
"server": server,
|
|
63
|
+
"url": url,
|
|
64
|
+
"params": params,
|
|
65
|
+
"alert_types": alert_types,
|
|
66
|
+
"webhook_url": webhook_url,
|
|
67
|
+
"callback": callback,
|
|
68
|
+
"started_at": time.time(),
|
|
69
|
+
"status": "starting"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Start streaming in background thread
|
|
73
|
+
if callback:
|
|
74
|
+
stream_thread = threading.Thread(
|
|
75
|
+
target=self._stream_alerts,
|
|
76
|
+
args=(session_info,),
|
|
77
|
+
daemon=True
|
|
78
|
+
)
|
|
79
|
+
stream_thread.start()
|
|
80
|
+
session_info["thread"] = stream_thread
|
|
81
|
+
session_info["status"] = "streaming"
|
|
82
|
+
|
|
83
|
+
report_usage("start_alert_stream", True, 0, {
|
|
84
|
+
"server": server,
|
|
85
|
+
"alert_types_count": len(alert_types),
|
|
86
|
+
"has_webhook": webhook_url is not None,
|
|
87
|
+
"has_callback": callback is not None
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return session_info
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
debug_print(f"❌ Failed to start alert stream: {e}", "error")
|
|
94
|
+
report_error(e, "start_alert_stream", {
|
|
95
|
+
"alert_types": alert_types,
|
|
96
|
+
"has_webhook": webhook_url is not None
|
|
97
|
+
})
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
def _stream_alerts(self, session_info: Dict[str, Any]):
|
|
101
|
+
"""
|
|
102
|
+
Internal method to handle alert streaming.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
session_info: Stream session information
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
import requests
|
|
109
|
+
|
|
110
|
+
url = session_info["url"]
|
|
111
|
+
params = session_info["params"]
|
|
112
|
+
callback = session_info.get("callback")
|
|
113
|
+
webhook_url = session_info.get("webhook_url")
|
|
114
|
+
|
|
115
|
+
debug_print("🌊 Connecting to alert stream...", "info")
|
|
116
|
+
|
|
117
|
+
# Make streaming request
|
|
118
|
+
response = requests.get(url, params=params, stream=True, timeout=30)
|
|
119
|
+
|
|
120
|
+
if response.status_code == 200:
|
|
121
|
+
debug_print("✅ Alert stream connected", "success")
|
|
122
|
+
|
|
123
|
+
# Process stream events
|
|
124
|
+
for line in response.iter_lines(decode_unicode=True):
|
|
125
|
+
if line and line.strip():
|
|
126
|
+
try:
|
|
127
|
+
# Parse alert data
|
|
128
|
+
if line.startswith("data: "):
|
|
129
|
+
alert_data = line[6:] # Remove "data: " prefix
|
|
130
|
+
alert_json = json.loads(alert_data)
|
|
131
|
+
|
|
132
|
+
# Process alert
|
|
133
|
+
self._process_alert(alert_json, callback, webhook_url)
|
|
134
|
+
|
|
135
|
+
except json.JSONDecodeError:
|
|
136
|
+
debug_print(f"⚠️ Invalid alert data: {line[:50]}...", "warning")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
debug_print(f"⚠️ Error processing alert: {e}", "warning")
|
|
139
|
+
else:
|
|
140
|
+
debug_print(f"❌ Alert stream failed: {response.status_code}", "error")
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
debug_print(f"❌ Alert stream error: {e}", "error")
|
|
144
|
+
report_error(e, "_stream_alerts", session_info)
|
|
145
|
+
|
|
146
|
+
def _process_alert(self,
|
|
147
|
+
alert_data: Dict[str, Any],
|
|
148
|
+
callback: Optional[Callable] = None,
|
|
149
|
+
webhook_url: Optional[str] = None):
|
|
150
|
+
"""
|
|
151
|
+
Process incoming alert data.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
alert_data: Alert data from stream
|
|
155
|
+
callback: Optional callback function
|
|
156
|
+
webhook_url: Optional webhook URL for forwarding
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
alert_type = alert_data.get("type", "unknown")
|
|
160
|
+
timestamp = alert_data.get("timestamp", time.time())
|
|
161
|
+
|
|
162
|
+
debug_print(f"🔔 Received alert: {alert_type}", "info")
|
|
163
|
+
|
|
164
|
+
# Format alert for processing
|
|
165
|
+
formatted_alert = {
|
|
166
|
+
"type": alert_type,
|
|
167
|
+
"timestamp": timestamp,
|
|
168
|
+
"data": alert_data,
|
|
169
|
+
"received_at": time.time()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Call callback if provided
|
|
173
|
+
if callback:
|
|
174
|
+
try:
|
|
175
|
+
callback(formatted_alert)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
debug_print(f"⚠️ Callback error: {e}", "warning")
|
|
178
|
+
|
|
179
|
+
# Forward to webhook if provided
|
|
180
|
+
if webhook_url:
|
|
181
|
+
try:
|
|
182
|
+
self._forward_alert_to_webhook(formatted_alert, webhook_url)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
debug_print(f"⚠️ Webhook forwarding error: {e}", "warning")
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
debug_print(f"❌ Error processing alert: {e}", "error")
|
|
188
|
+
|
|
189
|
+
def _forward_alert_to_webhook(self, alert: Dict[str, Any], webhook_url: str):
|
|
190
|
+
"""
|
|
191
|
+
Forward alert to Discord webhook.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
alert: Formatted alert data
|
|
195
|
+
webhook_url: Discord webhook URL
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
import requests
|
|
199
|
+
|
|
200
|
+
# Create Discord embed
|
|
201
|
+
alert_type = alert.get("type", "unknown")
|
|
202
|
+
timestamp = alert.get("timestamp", time.time())
|
|
203
|
+
|
|
204
|
+
embed = {
|
|
205
|
+
"title": f"🔔 MC5 Alert: {alert_type.upper()}",
|
|
206
|
+
"description": f"Real-time alert received from MC5 servers",
|
|
207
|
+
"color": 5814783, # Blue color
|
|
208
|
+
"fields": [
|
|
209
|
+
{
|
|
210
|
+
"name": "Alert Type",
|
|
211
|
+
"value": alert_type,
|
|
212
|
+
"inline": True
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "Timestamp",
|
|
216
|
+
"value": f"<t:{int(timestamp)}:R>",
|
|
217
|
+
"inline": True
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "Server",
|
|
221
|
+
"value": "eur-fedex servers",
|
|
222
|
+
"inline": True
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"footer": {
|
|
226
|
+
"text": "MC5 API Client - Real-time Alerts"
|
|
227
|
+
},
|
|
228
|
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Add alert data if available
|
|
232
|
+
alert_data = alert.get("data", {})
|
|
233
|
+
if alert_data:
|
|
234
|
+
# Add relevant data fields
|
|
235
|
+
for key, value in list(alert_data.items())[:5]: # Limit to 5 fields
|
|
236
|
+
if isinstance(value, (str, int, float, bool)):
|
|
237
|
+
embed["fields"].append({
|
|
238
|
+
"name": key.replace("_", " ").title(),
|
|
239
|
+
"value": str(value),
|
|
240
|
+
"inline": True
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
payload = {
|
|
244
|
+
"embeds": [embed],
|
|
245
|
+
"username": "MC5 Alerts"
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Send to webhook
|
|
249
|
+
response = requests.post(webhook_url, json=payload, timeout=10)
|
|
250
|
+
|
|
251
|
+
if response.status_code == 204:
|
|
252
|
+
debug_print(f"✅ Alert forwarded to webhook: {alert_type}", "success")
|
|
253
|
+
else:
|
|
254
|
+
debug_print(f"⚠️ Webhook response: {response.status_code}", "warning")
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
debug_print(f"❌ Webhook forwarding failed: {e}", "error")
|
|
258
|
+
raise
|
|
259
|
+
|
|
260
|
+
@debug_function
|
|
261
|
+
def test_alert_connection(self) -> Dict[str, Any]:
|
|
262
|
+
"""
|
|
263
|
+
Test connection to alert servers.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Connection test results
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
import requests
|
|
270
|
+
|
|
271
|
+
fedex_servers = [
|
|
272
|
+
"eur-fedex-fsg006.gameloft.com:45040",
|
|
273
|
+
"eur-fedex-fsg002.gameloft.com:41245",
|
|
274
|
+
"eur-fedex-fsg001.gameloft.com:45040"
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
results = []
|
|
278
|
+
|
|
279
|
+
for server in fedex_servers:
|
|
280
|
+
try:
|
|
281
|
+
url = f"https://{server}/alerts/me"
|
|
282
|
+
params = {
|
|
283
|
+
"access_token": self._token_data["access_token"],
|
|
284
|
+
"content_type": "event-stream",
|
|
285
|
+
"push_method": "streaming"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Test connection (short timeout)
|
|
289
|
+
response = requests.get(url, params=params, timeout=5, stream=True)
|
|
290
|
+
|
|
291
|
+
server_result = {
|
|
292
|
+
"server": server,
|
|
293
|
+
"status_code": response.status_code,
|
|
294
|
+
"connected": response.status_code == 200,
|
|
295
|
+
"response_time": response.elapsed.total_seconds() if hasattr(response, 'elapsed') else 0
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if response.status_code == 200:
|
|
299
|
+
debug_print(f"✅ {server}: Connected", "success")
|
|
300
|
+
else:
|
|
301
|
+
debug_print(f"❌ {server}: {response.status_code}", "error")
|
|
302
|
+
|
|
303
|
+
results.append(server_result)
|
|
304
|
+
|
|
305
|
+
# Close connection
|
|
306
|
+
response.close()
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
debug_print(f"❌ {server}: {e}", "error")
|
|
310
|
+
results.append({
|
|
311
|
+
"server": server,
|
|
312
|
+
"status_code": 0,
|
|
313
|
+
"connected": False,
|
|
314
|
+
"error": str(e)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
# Find best server
|
|
318
|
+
connected_servers = [r for r in results if r.get("connected")]
|
|
319
|
+
best_server = None
|
|
320
|
+
|
|
321
|
+
if connected_servers:
|
|
322
|
+
best_server = min(connected_servers, key=lambda x: x.get("response_time", float('inf')))
|
|
323
|
+
|
|
324
|
+
debug_print(f"🎯 Best server: {best_server.get('server') if best_server else 'None'}", "info")
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"results": results,
|
|
328
|
+
"connected_count": len(connected_servers),
|
|
329
|
+
"best_server": best_server,
|
|
330
|
+
"total_tested": len(results)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
except Exception as e:
|
|
334
|
+
debug_print(f"❌ Alert connection test failed: {e}", "error")
|
|
335
|
+
report_error(e, "test_alert_connection", {})
|
|
336
|
+
raise
|