mc5-api-client 1.0.16__py3-none-any.whl → 1.0.18__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,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
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env python3
2
+ # ────────────[ CHIZOBA ]────────────────────────────
3
+ # | Email : chizoba2026@hotmail.com
4
+ # | File : alerts_quick.py
5
+ # | License | MIT License © 2026 Chizoba
6
+ # | Brief | Quick functions for real-time alerts
7
+ # ────────────────★─────────────────────────────────
8
+
9
+ from typing import Optional, Dict, Any, List, Callable
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_start_alert_stream(username: str, password: str,
16
+ webhook_url: Optional[str] = None,
17
+ alert_types: Optional[List[str]] = None,
18
+ callback: Optional[Callable] = None) -> Optional[Dict[str, Any]]:
19
+ """
20
+ Quick function to start real-time alert streaming.
21
+
22
+ Args:
23
+ username: MC5 username
24
+ password: MC5 password
25
+ webhook_url: Optional Discord webhook URL for alert forwarding
26
+ alert_types: List of alert types to subscribe to
27
+ callback: Optional callback function for handling alerts
28
+
29
+ Returns:
30
+ Stream session information or None if failed
31
+ """
32
+ try:
33
+ with SimpleMC5Client(username, password) as client:
34
+ if client.connect():
35
+ session = client.client.start_alert_stream(
36
+ webhook_url=webhook_url,
37
+ alert_types=alert_types,
38
+ callback=callback
39
+ )
40
+
41
+ debug_print(f"✅ Alert stream started on {session.get('server')}", "success")
42
+ return session
43
+ return None
44
+ except Exception as e:
45
+ debug_print(f"❌ Failed to start alert stream: {e}", "error")
46
+ return None
47
+
48
+ @debug_function
49
+ def quick_test_alert_connection(username: str, password: str) -> Optional[Dict[str, Any]]:
50
+ """
51
+ Quick function to test alert server connections.
52
+
53
+ Args:
54
+ username: MC5 username
55
+ password: MC5 password
56
+
57
+ Returns:
58
+ Connection test results or None if failed
59
+ """
60
+ try:
61
+ with SimpleMC5Client(username, password) as client:
62
+ if client.connect():
63
+ results = client.client.test_alert_connection()
64
+
65
+ connected_count = results.get('connected_count', 0)
66
+ total_tested = results.get('total_tested', 0)
67
+
68
+ debug_print(f"✅ Alert connection test: {connected_count}/{total_tested} servers connected", "success")
69
+
70
+ best_server = results.get('best_server')
71
+ if best_server and best_server.get('connected'):
72
+ debug_print(f"🎯 Best server: {best_server.get('server')} ({best_server.get('response_time', 0):.2f}s)", "info")
73
+
74
+ return results
75
+ return None
76
+ except Exception as e:
77
+ debug_print(f"❌ Failed to test alert connection: {e}", "error")
78
+ return None
79
+
80
+ @debug_function
81
+ def quick_start_alerts_with_discord(username: str, password: str,
82
+ discord_webhook: str,
83
+ alert_types: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
84
+ """
85
+ Quick function to start alerts with Discord webhook forwarding.
86
+
87
+ Args:
88
+ username: MC5 username
89
+ password: MC5 password
90
+ discord_webhook: Discord webhook URL for alert forwarding
91
+ alert_types: List of alert types to subscribe to
92
+
93
+ Returns:
94
+ Stream session information or None if failed
95
+ """
96
+ try:
97
+ # Default alert types if not provided
98
+ if alert_types is None:
99
+ alert_types = ["connection", "message", "connection_request", "connection_request_accepted"]
100
+
101
+ debug_print(f"🔔 Starting alerts with Discord forwarding", "info")
102
+ debug_print(f"📋 Alert types: {', '.join(alert_types)}", "info")
103
+ debug_print(f"🔗 Webhook: {discord_webhook[:50]}...", "info")
104
+
105
+ return quick_start_alert_stream(
106
+ username=username,
107
+ password=password,
108
+ webhook_url=discord_webhook,
109
+ alert_types=alert_types
110
+ )
111
+ except Exception as e:
112
+ debug_print(f"❌ Failed to start alerts with Discord: {e}", "error")
113
+ return None
114
+
115
+ def create_alert_callback(print_alerts: bool = True, save_to_file: Optional[str] = None) -> Callable:
116
+ """
117
+ Create a callback function for handling alerts.
118
+
119
+ Args:
120
+ print_alerts: Whether to print alerts to console
121
+ save_to_file: Optional file path to save alerts
122
+
123
+ Returns:
124
+ Callback function
125
+ """
126
+ def alert_callback(alert_data: Dict[str, Any]):
127
+ """Handle incoming alert data."""
128
+ try:
129
+ alert_type = alert_data.get('type', 'unknown')
130
+ timestamp = alert_data.get('timestamp', 0)
131
+
132
+ # Format timestamp
133
+ import time
134
+ formatted_time = time.strftime("%H:%M:%S", time.localtime(timestamp))
135
+
136
+ # Print alert if requested
137
+ if print_alerts:
138
+ debug_print(f"🔔 [{formatted_time}] {alert_type.upper()}", "info")
139
+
140
+ # Print additional data if available
141
+ alert_info = alert_data.get('data', {})
142
+ if alert_info:
143
+ for key, value in list(alert_info.items())[:3]: # Show first 3 items
144
+ debug_print(f" {key}: {value}", "info")
145
+
146
+ # Save to file if requested
147
+ if save_to_file:
148
+ import json
149
+ with open(save_to_file, 'a', encoding='utf-8') as f:
150
+ f.write(json.dumps({
151
+ 'timestamp': timestamp,
152
+ 'type': alert_type,
153
+ 'data': alert_data
154
+ }) + '\n')
155
+
156
+ except Exception as e:
157
+ debug_print(f"❌ Error in alert callback: {e}", "error")
158
+
159
+ return alert_callback
160
+
161
+ @debug_function
162
+ def quick_monitor_alerts(username: str, password: str,
163
+ duration: int = 60,
164
+ discord_webhook: Optional[str] = None,
165
+ save_file: Optional[str] = None) -> int:
166
+ """
167
+ Quick function to monitor alerts for a specific duration.
168
+
169
+ Args:
170
+ username: MC5 username
171
+ password: MC5 password
172
+ duration: Duration in seconds to monitor
173
+ discord_webhook: Optional Discord webhook URL
174
+ save_file: Optional file to save alerts
175
+
176
+ Returns:
177
+ Number of alerts received
178
+ """
179
+ try:
180
+ import time
181
+
182
+ # Create callback
183
+ callback = create_alert_callback(print_alerts=True, save_to_file=save_file)
184
+
185
+ # Start alert stream
186
+ session = quick_start_alert_stream(
187
+ username=username,
188
+ password=password,
189
+ webhook_url=discord_webhook,
190
+ alert_types=["connection", "message", "connection_request", "connection_request_accepted"],
191
+ callback=callback
192
+ )
193
+
194
+ if not session:
195
+ return 0
196
+
197
+ debug_print(f"⏰ Monitoring alerts for {duration} seconds...", "info")
198
+
199
+ # Monitor for specified duration
200
+ time.sleep(duration)
201
+
202
+ debug_print(f"⏹️ Alert monitoring completed", "info")
203
+
204
+ # Note: In a real implementation, you'd want to track alert count
205
+ # For now, return a placeholder
206
+ return 0
207
+
208
+ except Exception as e:
209
+ debug_print(f"❌ Failed to monitor alerts: {e}", "error")
210
+ return 0