mc5-api-client 1.0.16__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,259 @@
1
+ #!/usr/bin/env python3
2
+ # ────────────[ CHIZOBA ]────────────────────────────
3
+ # | Email : chizoba2026@hotmail.com
4
+ # | File : debug.py
5
+ # | License | MIT License © 2026 Chizoba
6
+ # | Brief | Enhanced debugging utilities for MC5 API Client
7
+ # ────────────────★─────────────────────────────────
8
+
9
+ import sys
10
+ import traceback
11
+ import json
12
+ from typing import Optional, Dict, Any, Callable
13
+ from datetime import datetime
14
+ from .telemetry import is_telemetry_enabled, report_error
15
+
16
+ class DebugContext:
17
+ """Context manager for debugging function calls."""
18
+
19
+ def __init__(self, function_name: str, **kwargs):
20
+ self.function_name = function_name
21
+ self.kwargs = kwargs
22
+ self.start_time = None
23
+
24
+ def __enter__(self):
25
+ self.start_time = datetime.now()
26
+ # if is_telemetry_enabled():
27
+ # print(f"🔍 Debug: Starting {self.function_name}")
28
+ # if self.kwargs:
29
+ # print(f"🔍 Debug: Parameters: {json.dumps(self.kwargs, indent=2)}")
30
+ return self
31
+
32
+ def __exit__(self, exc_type, exc_val, exc_tb):
33
+ duration = (datetime.now() - self.start_time).total_seconds()
34
+
35
+ if exc_type is None:
36
+ # if is_telemetry_enabled():
37
+ # print(f"✅ Debug: {self.function_name} completed successfully ({duration:.2f}s)")
38
+ pass
39
+ else:
40
+ # if is_telemetry_enabled():
41
+ # print(f"❌ Debug: {self.function_name} failed after {duration:.2f}s")
42
+ # print(f"❌ Debug: Error: {exc_type.__name__}: {exc_val}")
43
+ # print(f"❌ Debug: Traceback:\n{traceback.format_exc()}")
44
+
45
+ # Report the error
46
+ report_error(exc_val, self.function_name, {
47
+ "duration": duration,
48
+ "parameters": self.kwargs
49
+ })
50
+
51
+ def debug_function(func: Callable) -> Callable:
52
+ """
53
+ Decorator to add debugging to any function.
54
+
55
+ Args:
56
+ func: Function to debug
57
+
58
+ Returns:
59
+ Wrapped function with debugging
60
+ """
61
+ def wrapper(*args, **kwargs):
62
+ function_name = f"{func.__module__}.{func.__name__}"
63
+
64
+ # Sanitize arguments for JSON serialization
65
+ safe_args = []
66
+ for arg in args:
67
+ if hasattr(arg, '__dict__'):
68
+ safe_args.append(f"<{type(arg).__name__} object>")
69
+ else:
70
+ safe_args.append(str(arg))
71
+
72
+ safe_kwargs = {}
73
+ for key, value in kwargs.items():
74
+ if hasattr(value, '__dict__'):
75
+ safe_kwargs[key] = f"<{type(value).__name__} object>"
76
+ else:
77
+ safe_kwargs[key] = str(value)
78
+
79
+ with DebugContext(function_name, args=safe_args, kwargs=safe_kwargs):
80
+ try:
81
+ result = func(*args, **kwargs)
82
+ return result
83
+ except Exception as e:
84
+ # Re-raise the exception after reporting
85
+ raise
86
+
87
+ return wrapper
88
+
89
+ def debug_print(message: str, level: str = "info") -> None:
90
+ """
91
+ Print debug message with timestamp and level.
92
+
93
+ Args:
94
+ message: Message to print
95
+ level: Debug level (info, warning, error, success)
96
+ """
97
+ timestamp = datetime.now().strftime("%H:%M:%S")
98
+
99
+ icons = {
100
+ "info": "🔍",
101
+ "warning": "⚠️",
102
+ "error": "❌",
103
+ "success": "✅",
104
+ "request": "📤",
105
+ "response": "📥"
106
+ }
107
+
108
+ icon = icons.get(level, "🔍")
109
+ print(f"[{timestamp}] {icon} {message}")
110
+
111
+ def debug_request(method: str, url: str, data: Optional[Dict[str, Any]] = None) -> None:
112
+ """
113
+ Debug HTTP request information.
114
+
115
+ Args:
116
+ method: HTTP method
117
+ url: Request URL
118
+ data: Request data
119
+ """
120
+ debug_print(f"{method} {url}", "request")
121
+ if data:
122
+ debug_print(f"Data: {json.dumps(data, indent=2)}", "info")
123
+
124
+ def debug_response(status_code: int, data: Optional[Dict[str, Any]] = None) -> None:
125
+ """
126
+ Debug HTTP response information.
127
+
128
+ Args:
129
+ status_code: HTTP status code
130
+ data: Response data
131
+ """
132
+ if 200 <= status_code < 300:
133
+ debug_print(f"Response {status_code}: Success", "success")
134
+ else:
135
+ debug_print(f"Response {status_code}: Error", "error")
136
+
137
+ if data:
138
+ # Truncate large responses for readability
139
+ data_str = json.dumps(data, indent=2)
140
+ if len(data_str) > 500:
141
+ data_str = data_str[:500] + "...\n[Response truncated]"
142
+ debug_print(f"Response data: {data_str}", "response")
143
+
144
+ def analyze_error(error: Exception) -> Dict[str, Any]:
145
+ """
146
+ Analyze an error and provide debugging information.
147
+
148
+ Args:
149
+ error: Exception to analyze
150
+
151
+ Returns:
152
+ Dictionary with error analysis
153
+ """
154
+ error_type = type(error).__name__
155
+ error_message = str(error)
156
+ traceback_str = traceback.format_exc()
157
+
158
+ analysis = {
159
+ "error_type": error_type,
160
+ "error_message": error_message,
161
+ "traceback": traceback_str,
162
+ "suggestions": []
163
+ }
164
+
165
+ # Provide specific suggestions based on error type
166
+ if "AuthenticationError" in error_type or "401" in error_message:
167
+ analysis["suggestions"].extend([
168
+ "Check your username and password",
169
+ "Verify your account is not banned",
170
+ "Try re-authenticating with a fresh token"
171
+ ])
172
+
173
+ elif "RateLimitError" in error_type or "429" in error_message:
174
+ analysis["suggestions"].extend([
175
+ "Wait before making more requests",
176
+ "Implement exponential backoff",
177
+ "Check rate limit headers for retry time"
178
+ ])
179
+
180
+ elif "NetworkError" in error_type or "timeout" in error_message.lower():
181
+ analysis["suggestions"].extend([
182
+ "Check your internet connection",
183
+ "Try increasing timeout value",
184
+ "Verify MC5 servers are accessible"
185
+ ])
186
+
187
+ elif "TokenExpiredError" in error_type:
188
+ analysis["suggestions"].extend([
189
+ "Enable auto_refresh in client",
190
+ "Call authenticate() again",
191
+ "Check token expiration time"
192
+ ])
193
+
194
+ return analysis
195
+
196
+ def print_error_analysis(error: Exception) -> None:
197
+ """
198
+ Print detailed error analysis with suggestions.
199
+
200
+ Args:
201
+ error: Exception to analyze
202
+ """
203
+ analysis = analyze_error(error)
204
+
205
+ debug_print(f"Error Analysis: {analysis['error_type']}", "error")
206
+ debug_print(f"Message: {analysis['error_message']}", "error")
207
+
208
+ if analysis["suggestions"]:
209
+ debug_print("Suggestions:", "warning")
210
+ for i, suggestion in enumerate(analysis["suggestions"], 1):
211
+ debug_print(f" {i}. {suggestion}", "warning")
212
+
213
+ def create_debug_report() -> Dict[str, Any]:
214
+ """
215
+ Create a comprehensive debug report.
216
+
217
+ Returns:
218
+ Debug report dictionary
219
+ """
220
+ import platform
221
+ import sys
222
+
223
+ report = {
224
+ "timestamp": datetime.now().isoformat(),
225
+ "python_version": sys.version,
226
+ "platform": platform.platform(),
227
+ "architecture": platform.architecture(),
228
+ "system": platform.system(),
229
+ "mc5_client_version": "1.0.16",
230
+ "telemetry_enabled": is_telemetry_enabled()
231
+ }
232
+
233
+ return report
234
+
235
+ def print_debug_report() -> None:
236
+ """Print a comprehensive debug report."""
237
+ report = create_debug_report()
238
+
239
+ debug_print("MC5 API Client Debug Report", "info")
240
+ debug_print("=" * 40, "info")
241
+
242
+ for key, value in report.items():
243
+ debug_print(f"{key}: {value}", "info")
244
+
245
+ # Convenience function for quick debugging
246
+ def debug_mode(enable: bool = False) -> None:
247
+ """
248
+ Enable or disable debug mode.
249
+
250
+ Args:
251
+ enable: Whether to enable debug mode (default: False)
252
+ """
253
+ if enable:
254
+ telemetry(True)
255
+ print_debug_report()
256
+ print("✅ Debug mode enabled")
257
+ else:
258
+ telemetry(False)
259
+ print("🔇 Debug mode disabled")
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env python3
2
+ # ────────────[ CHIZOBA ]────────────────────────────
3
+ # | Email : chizoba2026@hotmail.com
4
+ # | File : federation.py
5
+ # | License | MIT License © 2026 Chizoba
6
+ # | Brief | Federation and session management APIs
7
+ # ────────────────★─────────────────────────────────
8
+
9
+ import json
10
+ from typing import Optional, Dict, Any, List
11
+ from .telemetry import report_error, report_usage
12
+ from .debug import debug_function, debug_print
13
+
14
+ class FederationMixin:
15
+ """Mixin class for federation and session management functionality."""
16
+
17
+ # Federation endpoint
18
+ FEDERATION_BASE_URL = "https://federation-eur.gameloft.com"
19
+
20
+ @debug_function
21
+ def get_active_sessions(self, credentials: Optional[List[str]] = None) -> Dict[str, Any]:
22
+ """
23
+ Get active game sessions from federation server.
24
+
25
+ Args:
26
+ credentials: Optional list of user credentials to filter by
27
+
28
+ Returns:
29
+ Session information with owner details
30
+ """
31
+ try:
32
+ url = f"{self.FEDERATION_BASE_URL}/sessions/{self.client_id}"
33
+
34
+ # Always provide credentials parameter based on API discovery
35
+ if credentials:
36
+ credentials_str = ",".join(credentials)
37
+ else:
38
+ # Use current user's credential if none provided
39
+ user_credential = self._token_data.get("user_credential")
40
+ if not user_credential:
41
+ # Extract from access_token if needed
42
+ token_parts = self._token_data["access_token"].split(",")
43
+ for part in token_parts:
44
+ if "anonymous:" in part:
45
+ user_credential = part.strip()
46
+ break
47
+ credentials_str = user_credential if user_credential else ""
48
+
49
+ params = {
50
+ "access_token": self._token_data["access_token"],
51
+ "credentials": credentials_str
52
+ }
53
+
54
+ debug_print(f"🔍 Getting active sessions for {self.client_id}", "info")
55
+ result = self._make_request("GET", url, params=params)
56
+
57
+ if result:
58
+ sessions = result.get("sessions", [])
59
+ debug_print(f"✅ Found {len(sessions)} active sessions", "success")
60
+
61
+ # Analyze sessions
62
+ session_info = []
63
+ for session in sessions:
64
+ owner = session.get("owner", {})
65
+ session_data = {
66
+ "session_id": session.get("created"),
67
+ "client_id": session.get("client_id"),
68
+ "created": session.get("created"),
69
+ "owner_info": {
70
+ "fed_id": owner.get("fed_id"),
71
+ "credential": owner.get("credential"),
72
+ "name": owner.get("name"),
73
+ "server_type": owner.get("server_type"),
74
+ "country": owner.get("country")
75
+ }
76
+ }
77
+ session_info.append(session_data)
78
+
79
+ report_usage("get_active_sessions", True, 0, {
80
+ "sessions_found": len(sessions),
81
+ "client_id": self.client_id
82
+ })
83
+
84
+ return {
85
+ "sessions": session_info,
86
+ "total_count": len(sessions),
87
+ "client_id": self.client_id
88
+ }
89
+
90
+ return {"sessions": [], "total_count": 0}
91
+
92
+ except Exception as e:
93
+ debug_print(f"❌ Failed to get active sessions: {e}", "error")
94
+ report_error(e, "get_active_sessions", {
95
+ "client_id": self.client_id,
96
+ "credentials_count": len(credentials) if credentials else 0
97
+ })
98
+ raise
99
+
100
+ @debug_function
101
+ def get_my_sessions(self) -> Dict[str, Any]:
102
+ """
103
+ Get sessions for the current user.
104
+
105
+ Returns:
106
+ User's active sessions
107
+ """
108
+ try:
109
+ # Use current user's credential
110
+ user_credential = self._token_data.get("user_credential")
111
+ if not user_credential:
112
+ # Extract from access_token if needed
113
+ token_parts = self._token_data["access_token"].split(",")
114
+ for part in token_parts:
115
+ if "anonymous:" in part:
116
+ user_credential = part.strip()
117
+ break
118
+
119
+ if user_credential:
120
+ return self.get_active_sessions([user_credential])
121
+ else:
122
+ debug_print("⚠️ Could not determine user credential", "warning")
123
+ return {"sessions": [], "total_count": 0}
124
+
125
+ except Exception as e:
126
+ debug_print(f"❌ Failed to get user sessions: {e}", "error")
127
+ report_error(e, "get_my_sessions", {})
128
+ raise
129
+
130
+ @debug_function
131
+ def get_session_statistics(self) -> Dict[str, Any]:
132
+ """
133
+ Get statistics about active sessions.
134
+
135
+ Returns:
136
+ Session statistics and analytics
137
+ """
138
+ try:
139
+ sessions_data = self.get_active_sessions()
140
+ sessions = sessions_data.get("sessions", [])
141
+
142
+ if not sessions:
143
+ return {"error": "No sessions found"}
144
+
145
+ # Analyze session data
146
+ total_sessions = len(sessions)
147
+
148
+ # Server type distribution
149
+ server_types = {}
150
+ countries = {}
151
+
152
+ for session in sessions:
153
+ owner_info = session.get("owner_info", {})
154
+
155
+ # Count server types
156
+ server_type = owner_info.get("server_type", "unknown")
157
+ server_types[server_type] = server_types.get(server_type, 0) + 1
158
+
159
+ # Count countries
160
+ country = owner_info.get("country", "unknown")
161
+ countries[country] = countries.get(country, 0) + 1
162
+
163
+ # Recent sessions (last 5)
164
+ recent_sessions = sorted(sessions, key=lambda x: x.get("created", ""), reverse=True)[:5]
165
+
166
+ stats = {
167
+ "total_sessions": total_sessions,
168
+ "client_id": self.client_id,
169
+ "server_type_distribution": server_types,
170
+ "country_distribution": countries,
171
+ "recent_sessions": recent_sessions,
172
+ "analysis_timestamp": __import__('datetime').datetime.now().isoformat()
173
+ }
174
+
175
+ debug_print(f"✅ Session statistics analyzed: {total_sessions} sessions", "success")
176
+ return stats
177
+
178
+ except Exception as e:
179
+ debug_print(f"❌ Failed to analyze session statistics: {e}", "error")
180
+ report_error(e, "get_session_statistics", {})
181
+ raise
182
+
183
+ @debug_function
184
+ def check_session_status(self, fed_id: str) -> Dict[str, Any]:
185
+ """
186
+ Check status of a specific session by federation ID.
187
+
188
+ Args:
189
+ fed_id: Federation ID to check
190
+
191
+ Returns:
192
+ Session status information
193
+ """
194
+ try:
195
+ sessions_data = self.get_active_sessions()
196
+ sessions = sessions_data.get("sessions", [])
197
+
198
+ # Find session by fed_id
199
+ target_session = None
200
+ for session in sessions:
201
+ if session.get("owner_info", {}).get("fed_id") == fed_id:
202
+ target_session = session
203
+ break
204
+
205
+ if target_session:
206
+ debug_print(f"✅ Found session for fed_id: {fed_id}", "success")
207
+ return {
208
+ "found": True,
209
+ "session": target_session,
210
+ "status": "active"
211
+ }
212
+ else:
213
+ debug_print(f"⚠️ No session found for fed_id: {fed_id}", "warning")
214
+ return {
215
+ "found": False,
216
+ "status": "not_found"
217
+ }
218
+
219
+ except Exception as e:
220
+ debug_print(f"❌ Failed to check session status: {e}", "error")
221
+ report_error(e, "check_session_status", {"fed_id": fed_id})
222
+ raise
223
+
224
+ @debug_function
225
+ def get_server_type_sessions(self, server_type: str) -> Dict[str, Any]:
226
+ """
227
+ Get sessions filtered by server type.
228
+
229
+ Args:
230
+ server_type: Server type to filter (ts, mp_server, etc.)
231
+
232
+ Returns:
233
+ Filtered sessions by server type
234
+ """
235
+ try:
236
+ sessions_data = self.get_active_sessions()
237
+ sessions = sessions_data.get("sessions", [])
238
+
239
+ # Filter by server type
240
+ filtered_sessions = []
241
+ for session in sessions:
242
+ owner_info = session.get("owner_info", {})
243
+ if owner_info.get("server_type") == server_type:
244
+ filtered_sessions.append(session)
245
+
246
+ debug_print(f"✅ Found {len(filtered_sessions)} {server_type} sessions", "success")
247
+
248
+ return {
249
+ "server_type": server_type,
250
+ "sessions": filtered_sessions,
251
+ "total_count": len(filtered_sessions)
252
+ }
253
+
254
+ except Exception as e:
255
+ debug_print(f"❌ Failed to get {server_type} sessions: {e}", "error")
256
+ report_error(e, "get_server_type_sessions", {"server_type": server_type})
257
+ raise