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.
@@ -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
@@ -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