authlyx-api 0.1.0__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,5 @@
1
+ from .sdk import Auth, AuthlyX
2
+
3
+ __all__ = ["Auth", "AuthlyX"]
4
+ __version__ = "0.1.0"
5
+
authlyx_api/sdk.py ADDED
@@ -0,0 +1,573 @@
1
+ import requests
2
+ import json
3
+ import uuid
4
+ import hashlib
5
+ import os
6
+ import sys
7
+ import re
8
+ import time
9
+ import subprocess
10
+ from datetime import datetime, timezone
11
+
12
+
13
+ class AuthlyXLogger:
14
+ Enabled = True
15
+ AppName = "AuthlyX"
16
+
17
+ @staticmethod
18
+ def _mask_sensitive(text):
19
+ if not text:
20
+ return text
21
+ patterns = [
22
+ r'("session_id"\s*:\s*")([^"]+)(")',
23
+ r'("owner_id"\s*:\s*")([^"]+)(")',
24
+ r'("secret"\s*:\s*")([^"]+)(")',
25
+ r'("password"\s*:\s*")([^"]+)(")',
26
+ r'("key"\s*:\s*")([^"]+)(")',
27
+ r'("license_key"\s*:\s*")([^"]+)(")',
28
+ r'("hash"\s*:\s*")([^"]+)(")',
29
+ r'("request_id"\s*:\s*")([^"]+)(")',
30
+ r'("nonce"\s*:\s*")([^"]+)(")',
31
+ r'("hwid"\s*:\s*")([^"]+)(")',
32
+ r'("sid"\s*:\s*")([^"]+)(")',
33
+ r'(\bx-auth-signature\s*:\s*)([A-Za-z0-9+/=]+)',
34
+ r'(\bx-v2-signature\s*:\s*)([A-Za-z0-9+/=]+)',
35
+ ]
36
+ out = str(text)
37
+ for p in patterns:
38
+ out = re.sub(
39
+ p,
40
+ lambda m: (m.group(1) + "***" + (m.group(3) if m.lastindex and m.lastindex >= 3 else "")),
41
+ out,
42
+ flags=re.IGNORECASE,
43
+ )
44
+ return out
45
+
46
+ @staticmethod
47
+ def Log(content):
48
+ if not AuthlyXLogger.Enabled:
49
+ return
50
+ if content is None:
51
+ return
52
+ s = str(content)
53
+ if not s.strip():
54
+ return
55
+ try:
56
+ app = (AuthlyXLogger.AppName or "default").strip() or "default"
57
+ program_data = os.environ.get("PROGRAMDATA") or ""
58
+ root = os.path.join(program_data, "AuthlyX", app)
59
+ os.makedirs(root, exist_ok=True)
60
+ log_path = os.path.join(root, f"{datetime.now(timezone.utc):%Y_%m_%d}.log")
61
+ line = f"[{datetime.now(timezone.utc):%H:%M:%S}] {AuthlyXLogger._mask_sensitive(s)}\n"
62
+ with open(log_path, "a", encoding="utf-8") as f:
63
+ f.write(line)
64
+ except Exception:
65
+ return
66
+
67
+
68
+ class Auth:
69
+ DefaultBaseUrl = "https://authly.cc/api/v2"
70
+ IpLookupUrl = "https://api.ipify.org"
71
+
72
+ def __init__(self, ownerId, appName, version, secret, debug=True, api=None):
73
+ self.ownerId = ownerId or ""
74
+ self.appName = appName or ""
75
+ self.version = version or ""
76
+ self.secret = secret or ""
77
+ self.baseUrl = self._normalize_base_url(api or Auth.DefaultBaseUrl)
78
+ self.loggingEnabled = True if debug is None else bool(debug)
79
+
80
+ AuthlyXLogger.AppName = self.appName or "AuthlyX"
81
+ AuthlyXLogger.Enabled = self.loggingEnabled
82
+
83
+ self.sessionId = ""
84
+ self.applicationHash = ""
85
+ self.initialized = False
86
+ self.cachedPublicIp = ""
87
+ self.cachedPublicIpExpiresAt = 0.0
88
+
89
+ self.response = {
90
+ "success": False,
91
+ "message": "",
92
+ "raw": "",
93
+ "code": "",
94
+ "status_code": 0,
95
+ "request_id": "",
96
+ "nonce": "",
97
+ "signature_kid": "",
98
+ }
99
+
100
+ self.userData = {
101
+ "Username": "",
102
+ "Email": "",
103
+ "LicenseKey": "",
104
+ "Subscription": "",
105
+ "SubscriptionLevel": "",
106
+ "ExpiryDate": "",
107
+ "DaysLeft": 0,
108
+ "LastLogin": "",
109
+ "Hwid": "",
110
+ "IpAddress": "",
111
+ "RegisteredAt": "",
112
+ }
113
+
114
+ self.variableData = {"VarKey": "", "VarValue": "", "UpdatedAt": ""}
115
+
116
+ self.updateData = {
117
+ "Available": False,
118
+ "LatestVersion": "",
119
+ "DownloadUrl": "",
120
+ "ForceUpdate": False,
121
+ "Changelog": "",
122
+ "ShowReminder": False,
123
+ "ReminderMessage": "",
124
+ "AllowedUntil": "",
125
+ }
126
+
127
+ self.chatMessages = {
128
+ "ChannelName": "",
129
+ "Messages": [],
130
+ "Count": 0,
131
+ "NextCursor": "",
132
+ "HasMore": False,
133
+ }
134
+
135
+ self._calculate_application_hash()
136
+ AuthlyXLogger.Log(f"[SDK] AuthlyX initialized for app '{self.appName}' using '{self.baseUrl}'.")
137
+
138
+ def _normalize_base_url(self, api):
139
+ base = (api or "").strip()
140
+ if not base:
141
+ return Auth.DefaultBaseUrl
142
+ return base.rstrip("/")
143
+
144
+ def _reset_response(self):
145
+ self.response["success"] = False
146
+ self.response["message"] = ""
147
+ self.response["raw"] = ""
148
+ self.response["code"] = ""
149
+ self.response["status_code"] = 0
150
+ self.response["request_id"] = ""
151
+ self.response["nonce"] = ""
152
+ self.response["signature_kid"] = ""
153
+
154
+ def _set_failure(self, code, message, raw="", status_code=0):
155
+ self.response["success"] = False
156
+ self.response["code"] = code or ""
157
+ self.response["message"] = message or ""
158
+ self.response["raw"] = raw or ""
159
+ self.response["status_code"] = int(status_code or 0)
160
+ return False
161
+
162
+ def _has_required_credentials(self):
163
+ return bool(self.ownerId and self.appName and self.version and self.secret)
164
+
165
+ def _canonical_json(self, obj):
166
+ return json.dumps(obj, separators=(",", ":"), sort_keys=True, ensure_ascii=False)
167
+
168
+ def _create_security_context(self):
169
+ request_id = str(uuid.uuid4())
170
+ nonce = os.urandom(16).hex()
171
+ ts = int(time.time() * 1000)
172
+ return request_id, nonce, ts
173
+
174
+ def _calculate_application_hash(self):
175
+ try:
176
+ path = sys.executable
177
+ if not path or not os.path.exists(path):
178
+ path = os.path.abspath(sys.argv[0]) if sys.argv and sys.argv[0] else ""
179
+ if not path or not os.path.exists(path):
180
+ self.applicationHash = "UNKNOWN_HASH"
181
+ return
182
+ h = hashlib.sha256()
183
+ with open(path, "rb") as f:
184
+ while True:
185
+ b = f.read(1024 * 1024)
186
+ if not b:
187
+ break
188
+ h.update(b)
189
+ self.applicationHash = h.hexdigest()
190
+ except Exception:
191
+ self.applicationHash = "UNKNOWN_HASH"
192
+
193
+ def _get_windows_sid(self):
194
+ try:
195
+ out = subprocess.check_output(
196
+ ["whoami", "/user", "/fo", "csv", "/nh"],
197
+ stderr=subprocess.DEVNULL,
198
+ text=True,
199
+ ).strip()
200
+ if not out:
201
+ return ""
202
+ cols = [c.strip().strip('"') for c in out.split(",")]
203
+ for c in cols:
204
+ if c.startswith("S-1-"):
205
+ return c
206
+ if len(cols) >= 2 and cols[1].startswith("S-1-"):
207
+ return cols[1]
208
+ return ""
209
+ except Exception:
210
+ return ""
211
+
212
+ def _get_system_identifier(self):
213
+ if sys.platform == "win32":
214
+ sid = self._get_windows_sid()
215
+ if sid:
216
+ return sid
217
+ try:
218
+ seed = (os.environ.get("USERNAME") or "") + "|" + (os.environ.get("COMPUTERNAME") or "") + "|" + (sys.platform or "")
219
+ return hashlib.sha256(seed.encode("utf-8", errors="ignore")).hexdigest()
220
+ except Exception:
221
+ return "UNKNOWN_SID"
222
+
223
+ def _get_public_ip(self):
224
+ now = time.time()
225
+ if self.cachedPublicIp and now < self.cachedPublicIpExpiresAt:
226
+ return self.cachedPublicIp
227
+ try:
228
+ r = requests.get(Auth.IpLookupUrl, timeout=10)
229
+ ip = (r.text or "").strip()
230
+ if ip:
231
+ self.cachedPublicIp = ip
232
+ self.cachedPublicIpExpiresAt = now + 600.0
233
+ return ip
234
+ except Exception:
235
+ return self.cachedPublicIp or ""
236
+ return ""
237
+
238
+ def _build_url(self, endpoint):
239
+ ep = (endpoint or "").lstrip("/")
240
+ return f"{self.baseUrl}/{ep}"
241
+
242
+ def _validate_response_metadata(self, headers, request_id, nonce):
243
+ resp_request_id = headers.get("x-v2-request-id") or ""
244
+ resp_nonce = headers.get("x-v2-nonce") or ""
245
+ sig_kid = headers.get("x-v2-signature-kid") or ""
246
+ if resp_request_id and resp_request_id != request_id:
247
+ return False, "AUTH_REQUEST_MISMATCH", "Response request_id does not match the original request.", sig_kid
248
+ if resp_nonce and resp_nonce != nonce:
249
+ return False, "AUTH_REQUEST_MISMATCH", "Response nonce does not match the original request.", sig_kid
250
+ return True, "", "", sig_kid
251
+
252
+ def _compute_days_left(self, expiry):
253
+ try:
254
+ if not expiry:
255
+ return 0
256
+ s = str(expiry).strip()
257
+ if not s:
258
+ return 0
259
+ if s.endswith("Z"):
260
+ dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
261
+ else:
262
+ dt = datetime.fromisoformat(s)
263
+ diff = dt.timestamp() - time.time()
264
+ days = int(diff // (24 * 60 * 60))
265
+ return days if days > 0 else 0
266
+ except Exception:
267
+ return 0
268
+
269
+ def _load_update_data(self, obj):
270
+ try:
271
+ u = obj.get("update") if isinstance(obj, dict) else None
272
+ if not isinstance(u, dict):
273
+ return
274
+ self.updateData["Available"] = bool(u.get("available"))
275
+ self.updateData["LatestVersion"] = str(u.get("latest_version") or "")
276
+ self.updateData["DownloadUrl"] = u.get("download_url")
277
+ self.updateData["ForceUpdate"] = bool(u.get("force_update"))
278
+ self.updateData["Changelog"] = str(u.get("changelog") or "")
279
+ self.updateData["ShowReminder"] = bool(u.get("show_reminder"))
280
+ self.updateData["ReminderMessage"] = str(u.get("reminder_message") or "")
281
+ self.updateData["AllowedUntil"] = u.get("allowed_until")
282
+ except Exception:
283
+ return
284
+
285
+ def _load_user_data(self, obj):
286
+ if not isinstance(obj, dict):
287
+ return
288
+
289
+ user = obj.get("user") if isinstance(obj.get("user"), dict) else None
290
+ lic = obj.get("license") if isinstance(obj.get("license"), dict) else None
291
+ dev = obj.get("device") if isinstance(obj.get("device"), dict) else None
292
+
293
+ if user:
294
+ self.userData["Username"] = str(user.get("username") or "")
295
+ self.userData["Email"] = str(user.get("email") or self.userData.get("Email") or "")
296
+ self.userData["Subscription"] = str(user.get("subscription") or self.userData.get("Subscription") or "")
297
+ if user.get("subscription_level") is not None:
298
+ self.userData["SubscriptionLevel"] = str(user.get("subscription_level"))
299
+ self.userData["ExpiryDate"] = str(user.get("expiry_date") or self.userData.get("ExpiryDate") or "")
300
+ self.userData["LastLogin"] = str(user.get("last_login") or self.userData.get("LastLogin") or "")
301
+ self.userData["RegisteredAt"] = str(user.get("created_at") or user.get("registered_at") or self.userData.get("RegisteredAt") or "")
302
+ if user.get("ip_address"):
303
+ self.userData["IpAddress"] = str(user.get("ip_address") or "")
304
+ if user.get("sid") or user.get("hwid"):
305
+ self.userData["Hwid"] = str(user.get("sid") or user.get("hwid") or "")
306
+
307
+ if lic:
308
+ if lic.get("license_key"):
309
+ self.userData["LicenseKey"] = str(lic.get("license_key") or "")
310
+ if not self.userData.get("Subscription"):
311
+ self.userData["Subscription"] = str(lic.get("subscription") or "")
312
+ if not self.userData.get("SubscriptionLevel") and lic.get("subscription_level") is not None:
313
+ self.userData["SubscriptionLevel"] = str(lic.get("subscription_level"))
314
+ if not self.userData.get("ExpiryDate") and lic.get("expiry_date"):
315
+ self.userData["ExpiryDate"] = str(lic.get("expiry_date") or "")
316
+
317
+ if dev:
318
+ if not self.userData.get("Subscription") and dev.get("subscription"):
319
+ self.userData["Subscription"] = str(dev.get("subscription") or "")
320
+ if not self.userData.get("SubscriptionLevel") and dev.get("subscription_level") is not None:
321
+ self.userData["SubscriptionLevel"] = str(dev.get("subscription_level"))
322
+ if not self.userData.get("ExpiryDate") and dev.get("expiry_date"):
323
+ self.userData["ExpiryDate"] = str(dev.get("expiry_date") or "")
324
+ if not self.userData.get("LastLogin") and dev.get("last_login"):
325
+ self.userData["LastLogin"] = str(dev.get("last_login") or "")
326
+ if not self.userData.get("RegisteredAt") and (dev.get("registered_at") or dev.get("created_at")):
327
+ self.userData["RegisteredAt"] = str(dev.get("registered_at") or dev.get("created_at") or "")
328
+ if not self.userData.get("IpAddress") and dev.get("ip_address"):
329
+ self.userData["IpAddress"] = str(dev.get("ip_address") or "")
330
+ if not self.userData.get("Hwid") and (dev.get("hwid") or dev.get("sid")):
331
+ self.userData["Hwid"] = str(dev.get("sid") or dev.get("hwid") or "")
332
+
333
+ if not self.userData.get("Hwid"):
334
+ self.userData["Hwid"] = self._get_system_identifier()
335
+ if not self.userData.get("IpAddress"):
336
+ self.userData["IpAddress"] = self._get_public_ip()
337
+ self.userData["DaysLeft"] = self._compute_days_left(self.userData.get("ExpiryDate") or "")
338
+
339
+ def _load_variable_data(self, obj):
340
+ if not isinstance(obj, dict):
341
+ return
342
+ v = obj.get("variable") if isinstance(obj.get("variable"), dict) else None
343
+ if not v:
344
+ return
345
+ self.variableData["VarKey"] = str(v.get("var_key") or "")
346
+ self.variableData["VarValue"] = str(v.get("var_value") or "")
347
+ self.variableData["UpdatedAt"] = str(v.get("updated_at") or "")
348
+
349
+ def _load_chat_messages(self, obj):
350
+ if not isinstance(obj, dict):
351
+ return
352
+ chats = obj.get("chats") if isinstance(obj.get("chats"), dict) else None
353
+ if not chats:
354
+ return
355
+ self.chatMessages["ChannelName"] = str(chats.get("channel_name") or "")
356
+ self.chatMessages["Messages"] = chats.get("messages") if isinstance(chats.get("messages"), list) else []
357
+ self.chatMessages["Count"] = int(chats.get("count") or 0)
358
+ self.chatMessages["NextCursor"] = str(chats.get("next_cursor") or "")
359
+ self.chatMessages["HasMore"] = bool(chats.get("has_more"))
360
+
361
+ def _post_json(self, endpoint, payload):
362
+ self._reset_response()
363
+ if not self._has_required_credentials():
364
+ return self._set_failure("AUTH_CONFIG", "Missing AuthlyX credentials.")
365
+
366
+ url = self._build_url(endpoint)
367
+ request_id, nonce, ts = self._create_security_context()
368
+
369
+ body = dict(payload or {})
370
+ body["owner_id"] = self.ownerId
371
+ body["app_name"] = self.appName
372
+ body["version"] = self.version
373
+ body["secret"] = self.secret
374
+ body["request_id"] = request_id
375
+ body["nonce"] = nonce
376
+ body["timestamp"] = ts
377
+ body["hash"] = self.applicationHash
378
+
379
+ try:
380
+ AuthlyXLogger.Log(f"[SDK][REQUEST] POST {url} {self._canonical_json(body)}")
381
+ r = requests.post(
382
+ url,
383
+ json=body,
384
+ headers={
385
+ "x-request-id": request_id,
386
+ "x-auth-nonce": nonce,
387
+ "x-auth-timestamp": str(ts),
388
+ },
389
+ timeout=20,
390
+ )
391
+ text = r.text or ""
392
+ AuthlyXLogger.Log(f"[SDK][RESPONSE] {r.status_code} {text}")
393
+
394
+ self.response["status_code"] = int(r.status_code)
395
+ self.response["raw"] = text
396
+ self.response["request_id"] = request_id
397
+ self.response["nonce"] = nonce
398
+
399
+ ok, code, msg, kid = self._validate_response_metadata(r.headers or {}, request_id, nonce)
400
+ self.response["signature_kid"] = kid or ""
401
+ if not ok:
402
+ return self._set_failure(code, msg, text, r.status_code)
403
+
404
+ obj = None
405
+ try:
406
+ obj = r.json()
407
+ except Exception:
408
+ return self._set_failure("BAD_RESPONSE", "Invalid JSON response.", text, r.status_code)
409
+
410
+ self.response["success"] = bool(obj.get("success"))
411
+ self.response["message"] = str(obj.get("message") or "")
412
+ self.response["code"] = str(obj.get("code") or "")
413
+
414
+ if obj.get("session_id"):
415
+ self.sessionId = str(obj.get("session_id") or "")
416
+
417
+ if endpoint == "init":
418
+ self._load_update_data(obj)
419
+
420
+ if obj.get("success"):
421
+ self._load_user_data(obj)
422
+ self._load_variable_data(obj)
423
+ self._load_chat_messages(obj)
424
+
425
+ return bool(obj.get("success"))
426
+ except Exception as e:
427
+ return self._set_failure("NETWORK_ERROR", "Network error.", str(e), 0)
428
+
429
+ def _ensure_initialized(self):
430
+ if self.initialized and self.sessionId:
431
+ return True
432
+ return self._set_failure("NOT_INITIALIZED", "AuthlyX is not initialized. Call Init() first.")
433
+
434
+ def Init(self):
435
+ payload = {}
436
+ ok = self._post_json("init", payload)
437
+ self.initialized = bool(ok and self.sessionId)
438
+ return bool(ok)
439
+
440
+ def Login(self, identifier, password=None, deviceType=None):
441
+ if deviceType:
442
+ return self.DeviceLogin(deviceType, identifier)
443
+ if password is None:
444
+ return self.LicenseLogin(identifier)
445
+ return self.UsernameLogin(identifier, password)
446
+
447
+ def UsernameLogin(self, username, password):
448
+ if not self._ensure_initialized():
449
+ return False
450
+ payload = {
451
+ "session_id": self.sessionId,
452
+ "username": username or "",
453
+ "password": password or "",
454
+ "sid": self._get_system_identifier(),
455
+ "ip_address": self._get_public_ip(),
456
+ }
457
+ return self._post_json("login", payload)
458
+
459
+ def LicenseLogin(self, licenseKey):
460
+ if not self._ensure_initialized():
461
+ return False
462
+ payload = {
463
+ "session_id": self.sessionId,
464
+ "license_key": licenseKey or "",
465
+ "sid": self._get_system_identifier(),
466
+ "ip_address": self._get_public_ip(),
467
+ }
468
+ return self._post_json("licenses", payload)
469
+
470
+ def DeviceLogin(self, deviceType, deviceId):
471
+ if not self._ensure_initialized():
472
+ return False
473
+ payload = {
474
+ "session_id": self.sessionId,
475
+ "device_type": deviceType or "",
476
+ "device_id": deviceId or "",
477
+ "sid": self._get_system_identifier(),
478
+ "ip_address": self._get_public_ip(),
479
+ }
480
+ return self._post_json("device-auth", payload)
481
+
482
+ def Register(self, username, password, licenseKey, email=""):
483
+ if not self._ensure_initialized():
484
+ return False
485
+ payload = {
486
+ "session_id": self.sessionId,
487
+ "username": username or "",
488
+ "password": password or "",
489
+ "license_key": licenseKey or "",
490
+ "email": email or "",
491
+ "sid": self._get_system_identifier(),
492
+ "ip_address": self._get_public_ip(),
493
+ }
494
+ return self._post_json("register", payload)
495
+
496
+ def ChangePassword(self, oldPassword, newPassword):
497
+ if not self._ensure_initialized():
498
+ return False
499
+ payload = {"session_id": self.sessionId, "old_password": oldPassword or "", "new_password": newPassword or ""}
500
+ return self._post_json("change-password", payload)
501
+
502
+ def ExtendTime(self, username, licenseKey):
503
+ if not self._ensure_initialized():
504
+ return False
505
+ payload = {"session_id": self.sessionId, "username": username or "", "license_key": licenseKey or ""}
506
+ return self._post_json("extend", payload)
507
+
508
+ def GetVariable(self, varKey):
509
+ if not self._ensure_initialized():
510
+ return ""
511
+ payload = {"session_id": self.sessionId, "var_key": varKey or ""}
512
+ ok = self._post_json("variables", payload)
513
+ if ok:
514
+ return self.variableData.get("VarValue") or ""
515
+ return ""
516
+
517
+ def SetVariable(self, varKey, varValue):
518
+ if not self._ensure_initialized():
519
+ return False
520
+ payload = {"session_id": self.sessionId, "var_key": varKey or "", "var_value": varValue or ""}
521
+ return self._post_json("variables/set", payload)
522
+
523
+ def Log(self, message):
524
+ if not self._ensure_initialized():
525
+ return False
526
+ payload = {"session_id": self.sessionId, "message": message or ""}
527
+ return self._post_json("logs", payload)
528
+
529
+ def GetChats(self, channelName, limit=100, cursor=None):
530
+ if not self._ensure_initialized():
531
+ return False
532
+ payload = {"session_id": self.sessionId, "channel_name": channelName or "", "limit": int(limit or 100)}
533
+ if cursor:
534
+ payload["cursor"] = cursor
535
+ return self._post_json("chats/get", payload)
536
+
537
+ def SendChat(self, message, channelName=None):
538
+ if not self._ensure_initialized():
539
+ return False
540
+ payload = {"session_id": self.sessionId, "message": message or ""}
541
+ if channelName:
542
+ payload["channel_name"] = channelName
543
+ return self._post_json("chats/send", payload)
544
+
545
+ def ValidateSession(self):
546
+ if not self.initialized or not self.sessionId:
547
+ return self._set_failure("INVALID_SESSION", "No active session. Please login first.")
548
+ payload = {"session_id": self.sessionId}
549
+ return self._post_json("validate-session", payload)
550
+
551
+ def IsInitialized(self):
552
+ return bool(self.initialized)
553
+
554
+ def GetSessionId(self):
555
+ return self.sessionId or ""
556
+
557
+ def GetCurrentApplicationHash(self):
558
+ return self.applicationHash or ""
559
+
560
+ init = Init
561
+ login = Login
562
+ register = Register
563
+ extend_time = ExtendTime
564
+ get_variable = GetVariable
565
+ set_variable = SetVariable
566
+ get_chats = GetChats
567
+ send_chat = SendChat
568
+ validate_session = ValidateSession
569
+ change_password = ChangePassword
570
+
571
+
572
+ AuthlyX = Auth
573
+
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: authlyx-api
3
+ Version: 0.1.0
4
+ Summary: AuthlyX Python SDK (v2) for AuthlyX API integration.
5
+ Author: AuthlyX
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://authly.cc
8
+ Keywords: authlyx,authentication,license,sdk
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: requests>=2.31.0
16
+ Dynamic: license-file
17
+
18
+ # AuthlyX Python SDK (PyPI)
19
+
20
+ This folder is a publish-ready PyPI package version of the AuthlyX Python SDK.
21
+
22
+ Install:
23
+
24
+ ```bash
25
+ pip install authlyx-api
26
+ ```
27
+
28
+ Quick start:
29
+
30
+ ```py
31
+ from authlyx_api import AuthlyX
32
+
33
+ AuthlyXApp = AuthlyX(
34
+ ownerId="12345678",
35
+ appName="HI",
36
+ version="1.3",
37
+ secret="your-secret"
38
+ )
39
+
40
+ AuthlyXApp.Init()
41
+ if not AuthlyXApp.response["success"]:
42
+ raise Exception(AuthlyXApp.response["message"])
43
+
44
+ AuthlyXApp.Login("12", "1")
45
+ print(AuthlyXApp.userData["SubscriptionLevel"])
46
+ ```
47
+
48
+ Optional parameters:
49
+
50
+ ```py
51
+ from authlyx_api import AuthlyX
52
+
53
+ AuthlyXApp = AuthlyX(
54
+ ownerId="12345678",
55
+ appName="HI",
56
+ version="1.3",
57
+ secret="your-secret",
58
+ debug=False,
59
+ api="https://example.com/api/v2"
60
+ )
61
+ ```
62
+
63
+ Unified login:
64
+
65
+ ```py
66
+ # Username + password
67
+ AuthlyXApp.Login("12", "1")
68
+
69
+ # License key
70
+ AuthlyXApp.Login("XXXXX-XXXXX-XXXXX-XXXXX-XXXXX")
71
+
72
+ # Device login
73
+ AuthlyXApp.Login("YOUR_DEVICE_ID", deviceType="motherboard")
74
+ ```
75
+
76
+ Build locally:
77
+
78
+ ```bash
79
+ python -m pip install --upgrade build twine
80
+ python -m build
81
+ ```
82
+
83
+ Publish:
84
+
85
+ ```bash
86
+ twine upload dist/*
87
+ ```
88
+
@@ -0,0 +1,7 @@
1
+ authlyx_api/__init__.py,sha256=2EmCQaNw1Z0tEUAlEs8wKOxsy_UHvFwV9vHVQcMJ7hM,85
2
+ authlyx_api/sdk.py,sha256=76FagpkvJ7LP2lNGLnsDLYxo0T-ZS8K_3OsYHvX3tGY,22284
3
+ authlyx_api-0.1.0.dist-info/licenses/LICENSE,sha256=bILiEXG_2jCS3qMhXhjEWe1AqAqopRCSUOnB8NTfgzI,1065
4
+ authlyx_api-0.1.0.dist-info/METADATA,sha256=hUG3k13_ta3CRB7tChrbxGtYBqxYVbl6VqNU1opKIcU,1627
5
+ authlyx_api-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ authlyx_api-0.1.0.dist-info/top_level.txt,sha256=KY88Cbq_PvJZdlf7m1qBdbxUHwZcEPeU6DmohryDjSk,12
7
+ authlyx_api-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AuthlyX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1 @@
1
+ authlyx_api