Spaces:
Sleeping
Sleeping
Update licensing_client.py
Browse files- licensing_client.py +81 -80
licensing_client.py
CHANGED
|
@@ -17,7 +17,6 @@ from loguru import logger
|
|
| 17 |
STORAGE_FILE = "Hugging AbuAlone09/AI-Video-Engine-storage"
|
| 18 |
|
| 19 |
# ĐỌC BIẾN MÔI TRƯỜNG AN TOÀN TỪ SECRET CỦA HUGGING FACE
|
| 20 |
-
# Ông chỉ cần điền Link gốc và API Key vào ô Secret trên Settings của Space Tool chính, code tự động bốc lên chạy ngầm
|
| 21 |
SERVER_URL = os.getenv("LICENSIFY_SERVER_URL", "https://abualone09-my-licensify-server.hf.space").strip()
|
| 22 |
SECRET_API_KEY = os.getenv("SECRET_API_KEY", "YOUR_SECRET_API_KEY_HERE").strip()
|
| 23 |
|
|
@@ -28,7 +27,6 @@ if not os.path.exists(STORAGE_FILE):
|
|
| 28 |
json.dump({"keys": {}, "free_devices": {}}, f, indent=4)
|
| 29 |
|
| 30 |
# --- THÀNH PHẦN QUAN TRỌNG: QUẢN LÝ TRẠNG THÁI 6 LUỒNG TOÀN CỤC (MEMORY LOCK) ---
|
| 31 |
-
# Slot 1 -> 5: Ưu tiên VIP Premium. Slot 6: Dành riêng cho Free (VIP có quyền đẩy tiến trình)
|
| 32 |
THREAD_POOL = {
|
| 33 |
1: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
| 34 |
2: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
|
@@ -38,6 +36,11 @@ THREAD_POOL = {
|
|
| 38 |
6: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
| 39 |
}
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
# =========================================================
|
| 42 |
# CORE 1: XỬ LÝ ĐỌC / GHI & XÁC THỰC API KEY TỪ XA TỚI SERVER
|
| 43 |
# =========================================================
|
|
@@ -56,14 +59,12 @@ def clean_expired_keys():
|
|
| 56 |
now_ts = time.time()
|
| 57 |
changed = False
|
| 58 |
|
| 59 |
-
# Quét sạch VIP Key hết hạn khỏi hệ thống lưu trữ
|
| 60 |
for k, info in list(data["keys"].items()):
|
| 61 |
if now_ts > info.get("expiry_timestamp", 0):
|
| 62 |
del data["keys"][k]
|
| 63 |
changed = True
|
| 64 |
logger.warning(f"Key {k} expired and has been automatically purged from storage.")
|
| 65 |
|
| 66 |
-
# Reset lượt Free nếu hệ thống chuyển dịch sang ngày mới
|
| 67 |
today_str = datetime.now().strftime("%Y-%m-%d")
|
| 68 |
for dev, info in list(data["free_devices"].items()):
|
| 69 |
if info.get("last_date") != today_str:
|
|
@@ -75,51 +76,77 @@ def clean_expired_keys():
|
|
| 75 |
_save_storage(data)
|
| 76 |
|
| 77 |
def verify_and_get_license_info(key_input: str, device_id: str) -> tuple:
|
| 78 |
-
"""Kiểm tra cục bộ trước, nếu không
|
| 79 |
clean_expired_keys()
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
return True, {
|
| 84 |
-
"tier": "
|
| 85 |
-
"
|
| 86 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
|
| 89 |
data = _load_storage()
|
| 90 |
|
| 91 |
-
#
|
| 92 |
-
if
|
| 93 |
-
info = data["keys"][
|
| 94 |
days_left = int((info["expiry_timestamp"] - time.time()) / 86400)
|
| 95 |
return True, {
|
| 96 |
"tier": "VIP", "tx_name": info["tx_name"], "amount": info["amount"],
|
| 97 |
"tx_date": info["tx_date"], "expiry": info["expiry"], "days_left": max(0, days_left),
|
| 98 |
-
"msg": f"👑 VIP ACTIVE | User: {info['tx_name']} | {max(0, days_left)} days remaining."
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
-
#
|
| 102 |
try:
|
| 103 |
-
# Gài mã bảo mật Secret nội bộ vào lớp vỏ Header để thông quan
|
| 104 |
headers = {
|
| 105 |
"X-API-Key": SECRET_API_KEY,
|
| 106 |
"Content-Type": "application/json"
|
| 107 |
}
|
| 108 |
-
payload = {"key":
|
| 109 |
-
|
| 110 |
-
# Gọi lệnh POST gửi dữ liệu JSON thông mạch tới cổng dịch vụ của ông
|
| 111 |
response = requests.post(f"{SERVER_URL}/api/verify-key", json=payload, headers=headers, timeout=10)
|
| 112 |
|
| 113 |
if response.status_code == 200:
|
| 114 |
res_data = response.json()
|
| 115 |
if res_data.get("status") == "success":
|
| 116 |
-
# Bóc tách chuẩn xác toàn bộ thông tin giao dịch cần giám sát từ Server dịch vụ trả về
|
| 117 |
tx_info = res_data.get("data", {})
|
| 118 |
-
expiry_str = tx_info.get("expiry_date")
|
| 119 |
expiry_ts = time.mktime(time.strptime(expiry_str, "%Y-%m-%d"))
|
| 120 |
|
| 121 |
-
|
| 122 |
-
data["keys"][key_input] = {
|
| 123 |
"tx_name": tx_info.get("buyer_name", "Anonymous User"),
|
| 124 |
"amount": tx_info.get("amount_paid", "$2.99"),
|
| 125 |
"tx_date": tx_info.get("payment_date", datetime.now().strftime("%Y-%m-%d")),
|
|
@@ -132,19 +159,21 @@ def verify_and_get_license_info(key_input: str, device_id: str) -> tuple:
|
|
| 132 |
return True, {
|
| 133 |
"tier": "VIP", "tx_name": tx_info.get("buyer_name"), "amount": tx_info.get("amount_paid"),
|
| 134 |
"tx_date": tx_info.get("payment_date"), "expiry": expiry_str, "days_left": max(0, days_left),
|
| 135 |
-
"msg": f"👑 VIP KEY VERIFIED | Owner: {tx_info.get('buyer_name')} |
|
|
|
|
|
|
|
|
|
|
| 136 |
}
|
| 137 |
-
return False, {"msg": "❌ Invalid
|
| 138 |
except Exception as e:
|
| 139 |
-
return False, {"msg": f"⚠️ Connection error to Licensify Server!
|
| 140 |
|
| 141 |
# =========================================================
|
| 142 |
# CORE 2: CƠ CHẾ ĐIỀU PHỐI 6 LUỒNG ĐỘNG & BIÊN TRỰC VIP / FREE
|
| 143 |
# =========================================================
|
| 144 |
|
| 145 |
def get_thread_status_json():
|
| 146 |
-
|
| 147 |
-
busy_count = sum(1 for t in THREAD_POOL.values() if t["status"] == "rendering")
|
| 148 |
vip_count = sum(1 for t in THREAD_POOL.values() if t["type"] == "VIP")
|
| 149 |
free_count = sum(1 for t in THREAD_POOL.values() if t["type"] == "FREE")
|
| 150 |
return {
|
|
@@ -155,71 +184,56 @@ def get_thread_status_json():
|
|
| 155 |
}
|
| 156 |
|
| 157 |
def allocate_render_thread(key_input: str, device_id: str, is_vip: bool) -> tuple:
|
| 158 |
-
"""
|
| 159 |
-
Thuật toán phân phối 6 luồng động cao cấp:
|
| 160 |
-
- Mỗi VIP Key chỉ được tạo duy nhất 1 luồng/thiết bị, chống cắm đa luồng trùng lặp.
|
| 161 |
-
- VIP tự động đẩy tiến trình Free tại Slot 6 nếu hệ thống đầy.
|
| 162 |
-
- Chặn đứng mọi k���t nối vượt ngưỡng (Người thứ 7 báo đầy luồng).
|
| 163 |
-
"""
|
| 164 |
global THREAD_POOL
|
| 165 |
|
| 166 |
-
# Khóa đơn luồng thiết bị: Kiểm tra trùng lặp tiến trình đang xử lý
|
| 167 |
for slot, thread in THREAD_POOL.items():
|
| 168 |
if thread["status"] == "rendering":
|
| 169 |
-
if is_vip and thread["key"] == key_input:
|
| 170 |
-
return False, "❌ This VIP Key is already running a rendering process
|
| 171 |
if not is_vip and thread["device"] == device_id:
|
| 172 |
return False, "❌ Your device is currently rendering a video. Please wait until it completes!"
|
| 173 |
|
| 174 |
target_slot = None
|
| 175 |
|
| 176 |
if is_vip:
|
| 177 |
-
# VIP quét tìm không gian slot trống từ Slot 1 đến Slot 5
|
| 178 |
for i in range(1, 6):
|
| 179 |
if THREAD_POOL[i]["status"] == "idle":
|
| 180 |
target_slot = i
|
| 181 |
break
|
| 182 |
|
| 183 |
-
# Nếu 5 slot VIP đã full, VIP tiến hành kiểm tra chiếm dụng Slot số 6
|
| 184 |
if not target_slot:
|
| 185 |
if THREAD_POOL[6]["status"] == "idle":
|
| 186 |
target_slot = 6
|
| 187 |
elif THREAD_POOL[6]["type"] == "FREE":
|
| 188 |
-
# 🔥 CƠ CHẾ ĐẨY TIẾN TRÌNH: VIP chiếm chỗ, gửi tín hiệu hủy trực tiếp PID của Free
|
| 189 |
free_pid = THREAD_POOL[6]["pid"]
|
| 190 |
-
logger.warning(f"👑 VIP Eviction Triggered: Terminating Free PID {free_pid} at Slot 6
|
| 191 |
try:
|
| 192 |
if free_pid:
|
| 193 |
-
os.kill(free_pid, signal.SIGKILL)
|
| 194 |
except ProcessLookupError:
|
| 195 |
pass
|
| 196 |
|
| 197 |
target_slot = 6
|
| 198 |
-
# Trả Slot 6 về mặc định trống để nạp đè VIP vào luôn
|
| 199 |
THREAD_POOL[6] = {"status": "idle", "key": None, "type": None, "pid": None, "device": None}
|
| 200 |
else:
|
| 201 |
-
# Tài khoản Free: Chỉ được phép nạp vào Slot số 6 nếu hoàn toàn trống
|
| 202 |
if THREAD_POOL[6]["status"] == "idle":
|
| 203 |
target_slot = 6
|
| 204 |
|
| 205 |
-
# BIÊN CHẶN NGƯỜI THỨ 7: Toàn bộ 6 luồng hệ thống đã đạt đỉnh tải
|
| 206 |
if not target_slot:
|
| 207 |
return False, "⚠️ All rendering channels are currently full. Please try again in a few moments!"
|
| 208 |
|
| 209 |
return True, target_slot
|
| 210 |
|
| 211 |
def register_process_to_slot(slot: int, key_input: str, device_id: str, is_vip: bool, pid: int):
|
| 212 |
-
"""Ghi nhận định danh tiến trình hệ thống (PID) vào sơ đồ luồng"""
|
| 213 |
THREAD_POOL[slot] = {
|
| 214 |
"status": "rendering",
|
| 215 |
-
"key": key_input if is_vip else None,
|
| 216 |
"type": "VIP" if is_vip else "FREE",
|
| 217 |
"pid": pid,
|
| 218 |
"device": device_id
|
| 219 |
}
|
| 220 |
|
| 221 |
def release_thread_slot(slot: int):
|
| 222 |
-
"""Giải phóng trạng thái slot sau khi tiến trình hoàn tất hoặc xuất file thành công"""
|
| 223 |
if slot in THREAD_POOL:
|
| 224 |
THREAD_POOL[slot] = {"status": "idle", "key": None, "type": None, "pid": None, "device": None}
|
| 225 |
|
|
@@ -228,7 +242,6 @@ def release_thread_slot(slot: int):
|
|
| 228 |
# =========================================================
|
| 229 |
|
| 230 |
def check_generation_limits(key_input: str, device_id: str, is_vip: bool) -> tuple:
|
| 231 |
-
"""Kiểm tra hạn mức tạo video sát sao theo thời gian thực"""
|
| 232 |
data = _load_storage()
|
| 233 |
today_str = datetime.now().strftime("%Y-%m-%d")
|
| 234 |
now_ts = time.time()
|
|
@@ -238,33 +251,31 @@ def check_generation_limits(key_input: str, device_id: str, is_vip: bool) -> tup
|
|
| 238 |
return False, "License error: Key missing from verified list."
|
| 239 |
|
| 240 |
vip_data = data["keys"][key_input]
|
| 241 |
-
# Khởi tạo bộ đếm mới nếu bước sang ngày mới theo chu kỳ cổng thanh toán cung cấp
|
| 242 |
if vip_data.get("last_date_used") != today_str:
|
| 243 |
vip_data["last_date_used"] = today_str
|
| 244 |
-
vip_data["daily_batches"] = 0
|
| 245 |
-
vip_data["videos_this_batch"] = 0
|
| 246 |
vip_data["cooldown_until"] = 0
|
| 247 |
|
| 248 |
if now_ts < vip_data.get("cooldown_until", 0):
|
| 249 |
wait_min = int((vip_data["cooldown_until"] - now_ts) / 60)
|
| 250 |
-
return False, f"⏱️ Cooldown active!
|
| 251 |
|
| 252 |
if vip_data.get("daily_batches", 0) >= 5:
|
| 253 |
-
return False, "❌ You have exhausted your daily limit of 5 batches
|
| 254 |
|
| 255 |
remaining_batches = 5 - vip_data.get("daily_batches", 0)
|
| 256 |
return True, {
|
| 257 |
"videos_created": vip_data.get("videos_this_batch", 0),
|
| 258 |
"batches_created": vip_data.get("daily_batches", 0),
|
| 259 |
"remaining_batches": remaining_batches,
|
| 260 |
-
"status_str": f"VIP Usage: Batch {vip_data.get('daily_batches', 0)}/5 |
|
| 261 |
}
|
| 262 |
else:
|
| 263 |
-
# Cơ chế nhận diện thiết bị chống spam cho khách hàng không dùng key (FREE)
|
| 264 |
if device_id not in data["free_devices"]:
|
| 265 |
data["free_devices"][device_id] = {
|
| 266 |
"last_date": today_str,
|
| 267 |
-
"daily_batches": 0,
|
| 268 |
"cooldown_until": 0
|
| 269 |
}
|
| 270 |
|
|
@@ -279,7 +290,7 @@ def check_generation_limits(key_input: str, device_id: str, is_vip: bool) -> tup
|
|
| 279 |
return False, f"⏱️ Free Tier Cooldown: Please wait {wait_hours + 1} hours before generating again, or upgrade to VIP Premium!"
|
| 280 |
|
| 281 |
if free_data.get("daily_batches", 0) >= 3:
|
| 282 |
-
return False, "❌ Free Tier Exhausted! Maximum 3 daily videos reached.
|
| 283 |
|
| 284 |
remaining_free = 3 - free_data.get("daily_batches", 0)
|
| 285 |
return True, {
|
|
@@ -290,26 +301,22 @@ def check_generation_limits(key_input: str, device_id: str, is_vip: bool) -> tup
|
|
| 290 |
}
|
| 291 |
|
| 292 |
def commit_generation_success(key_input: str, device_id: str, is_vip: bool):
|
| 293 |
-
"""
|
| 294 |
-
HÀM GHI NHẬN HẠN MỨC: Chỉ kích hoạt ghi đè khi video đã thành công và xuất xưởng 100%.
|
| 295 |
-
Nếu tài khoản Free bị VIP đẩy tiến trình giữa chừng -> Không chạy qua hàm này -> Bảo toàn lượt tạo.
|
| 296 |
-
"""
|
| 297 |
data = _load_storage()
|
| 298 |
now_ts = time.time()
|
| 299 |
|
| 300 |
if is_vip:
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
vip_data["cooldown_until"] = now_ts + 3600 # Bắt đầu tính hàng chờ khóa 1 tiếng
|
| 309 |
else:
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
|
|
|
| 313 |
|
| 314 |
_save_storage(data)
|
| 315 |
|
|
@@ -320,22 +327,16 @@ def commit_generation_success(key_input: str, device_id: str, is_vip: bool):
|
|
| 320 |
def execute_admin_diagnostic_test() -> str:
|
| 321 |
"""Khi Admin nhấn nút Test trên run_app.py, hàm này gọi ngầm file tester.py để hứng dữ liệu báo cáo trả lên UI"""
|
| 322 |
test_script = "tester.py"
|
| 323 |
-
|
| 324 |
-
# Kiểm tra sự tồn tại của file kiểm thử trên phân vùng hệ thống
|
| 325 |
if not os.path.exists(test_script):
|
| 326 |
return f"❌ FILE MISSING: Tệp '{test_script}' không tồn tại trong thư mục chạy hệ thống!"
|
| 327 |
|
| 328 |
try:
|
| 329 |
-
# Gọi tệp kiểm thử độc lập chạy ngầm thông qua subprocess hệ thống
|
| 330 |
result = subprocess.run(["python", test_script], capture_output=True, text=True, timeout=15)
|
| 331 |
-
|
| 332 |
-
# Hứng toàn bộ kết quả đầu ra (stdout) hoặc thông báo lỗi (stderr) để bắn về khung thông báo run_app
|
| 333 |
if result.returncode == 0:
|
| 334 |
return f"✅ [TESTER REPORT SUCCESS]:\n{result.stdout.strip()}"
|
| 335 |
else:
|
| 336 |
return f"❌ [TESTER REPORT CRASHED WITH EXIT CODE {result.returncode}]:\n{result.stderr.strip()}"
|
| 337 |
except subprocess.TimeoutExpired:
|
| 338 |
-
return "⚠️ [TESTER TIMEOUT]: Tiến trình kiểm thử
|
| 339 |
except Exception as e:
|
| 340 |
return f"❌ [SYSTEM CRASH]: Không thể kích hoạt được tiến trình kiểm thử. Lỗi: {str(e)}"
|
| 341 |
-
|
|
|
|
| 17 |
STORAGE_FILE = "Hugging AbuAlone09/AI-Video-Engine-storage"
|
| 18 |
|
| 19 |
# ĐỌC BIẾN MÔI TRƯỜNG AN TOÀN TỪ SECRET CỦA HUGGING FACE
|
|
|
|
| 20 |
SERVER_URL = os.getenv("LICENSIFY_SERVER_URL", "https://abualone09-my-licensify-server.hf.space").strip()
|
| 21 |
SECRET_API_KEY = os.getenv("SECRET_API_KEY", "YOUR_SECRET_API_KEY_HERE").strip()
|
| 22 |
|
|
|
|
| 27 |
json.dump({"keys": {}, "free_devices": {}}, f, indent=4)
|
| 28 |
|
| 29 |
# --- THÀNH PHẦN QUAN TRỌNG: QUẢN LÝ TRẠNG THÁI 6 LUỒNG TOÀN CỤC (MEMORY LOCK) ---
|
|
|
|
| 30 |
THREAD_POOL = {
|
| 31 |
1: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
| 32 |
2: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
|
|
|
| 36 |
6: {"status": "idle", "key": None, "type": None, "pid": None, "device": None},
|
| 37 |
}
|
| 38 |
|
| 39 |
+
# --- BỔ SUNG HÀM ĐỂ BÊN RUN_APP.PY GỌI ĐỒNG BỘ REALTIME THEO GIÂY MÀ KHÔNG BỊ LỖI ---
|
| 40 |
+
def get_active_threads_count() -> int:
|
| 41 |
+
"""Trả về tổng số luồng thực tế đang bận render trong hệ thống cluster"""
|
| 42 |
+
return sum(1 for t in THREAD_POOL.values() if t["status"] == "rendering")
|
| 43 |
+
|
| 44 |
# =========================================================
|
| 45 |
# CORE 1: XỬ LÝ ĐỌC / GHI & XÁC THỰC API KEY TỪ XA TỚI SERVER
|
| 46 |
# =========================================================
|
|
|
|
| 59 |
now_ts = time.time()
|
| 60 |
changed = False
|
| 61 |
|
|
|
|
| 62 |
for k, info in list(data["keys"].items()):
|
| 63 |
if now_ts > info.get("expiry_timestamp", 0):
|
| 64 |
del data["keys"][k]
|
| 65 |
changed = True
|
| 66 |
logger.warning(f"Key {k} expired and has been automatically purged from storage.")
|
| 67 |
|
|
|
|
| 68 |
today_str = datetime.now().strftime("%Y-%m-%d")
|
| 69 |
for dev, info in list(data["free_devices"].items()):
|
| 70 |
if info.get("last_date") != today_str:
|
|
|
|
| 76 |
_save_storage(data)
|
| 77 |
|
| 78 |
def verify_and_get_license_info(key_input: str, device_id: str) -> tuple:
|
| 79 |
+
"""Kiểm tra cục bộ qua Hugging Face Secret trước, nếu không khớp gọi API sang Server đối soát"""
|
| 80 |
clean_expired_keys()
|
| 81 |
|
| 82 |
+
token = key_input.strip()
|
| 83 |
+
|
| 84 |
+
# LẤY VIP KEY VÀ ADMIN KEY AN TOÀN TRỰC TIẾP TỪ MỤC SECRET CỦA HUGGING FACE
|
| 85 |
+
admin_secret = os.getenv("ADMIN_SECRET_KEY", "ADMIN_SECRET_TEST_2026").strip()
|
| 86 |
+
vip_secret = os.getenv("VIP_SECRET_KEY", "VIP_EARLY_ACCESS_2026").strip()
|
| 87 |
+
|
| 88 |
+
# 1. Cơ chế đăng nhập bằng mã Admin tối cao lấy từ Secret (Không giới hạn, mở Panel Test)
|
| 89 |
+
if token == admin_secret:
|
| 90 |
+
return True, {
|
| 91 |
+
"tier": "ADMIN",
|
| 92 |
+
"tx_name": "System Master Administrator",
|
| 93 |
+
"amount": "$0.00 (Root Privilege)",
|
| 94 |
+
"tx_date": datetime.now().strftime("%Y-%m-%d"),
|
| 95 |
+
"expiry": "Permanent Access",
|
| 96 |
+
"days_left": 9999,
|
| 97 |
+
"msg": "📋 SYSTEM STATUS: Admin Master Active. Full hardware diagnostic test layer unlocked.",
|
| 98 |
+
"show_test_panel": True,
|
| 99 |
+
"remove_watermark": True,
|
| 100 |
+
"bypass_limits": True
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
# 2. Cơ chế đặc quyền ưu đãi dành cho VIP Key lấy từ Secret (Bypass giới hạn, không hiện Panel Test)
|
| 104 |
+
if token == vip_secret:
|
| 105 |
return True, {
|
| 106 |
+
"tier": "VIP",
|
| 107 |
+
"tx_name": "VIP Promotional Access",
|
| 108 |
+
"amount": "$0.00 (Promo Tier)",
|
| 109 |
+
"tx_date": datetime.now().strftime("%Y-%m-%d"),
|
| 110 |
+
"expiry": "2026-12-31",
|
| 111 |
+
"days_left": 200,
|
| 112 |
+
"msg": "👑 VIP STATUS: Promo Key Verified. Watermarks and daily rendering limits removed.",
|
| 113 |
+
"show_test_panel": False,
|
| 114 |
+
"remove_watermark": True,
|
| 115 |
+
"bypass_limits": True
|
| 116 |
}
|
| 117 |
|
| 118 |
data = _load_storage()
|
| 119 |
|
| 120 |
+
# 3. Tra cứu kho lưu trữ cục bộ trước để tăng tốc độ phản hồi cho giao diện điện thoại
|
| 121 |
+
if token in data["keys"]:
|
| 122 |
+
info = data["keys"][token]
|
| 123 |
days_left = int((info["expiry_timestamp"] - time.time()) / 86400)
|
| 124 |
return True, {
|
| 125 |
"tier": "VIP", "tx_name": info["tx_name"], "amount": info["amount"],
|
| 126 |
"tx_date": info["tx_date"], "expiry": info["expiry"], "days_left": max(0, days_left),
|
| 127 |
+
"msg": f"👑 VIP ACTIVE | User: {info['tx_name']} | {max(0, days_left)} days remaining.",
|
| 128 |
+
"show_test_panel": False,
|
| 129 |
+
"remove_watermark": True,
|
| 130 |
+
"bypass_limits": False
|
| 131 |
}
|
| 132 |
|
| 133 |
+
# 4. Giao tiếp API an toàn từ xa kết nối thẳng tới Kho lưu trữ của Cổng thanh toán dịch vụ công khai
|
| 134 |
try:
|
|
|
|
| 135 |
headers = {
|
| 136 |
"X-API-Key": SECRET_API_KEY,
|
| 137 |
"Content-Type": "application/json"
|
| 138 |
}
|
| 139 |
+
payload = {"key": token, "hwid": device_id}
|
|
|
|
|
|
|
| 140 |
response = requests.post(f"{SERVER_URL}/api/verify-key", json=payload, headers=headers, timeout=10)
|
| 141 |
|
| 142 |
if response.status_code == 200:
|
| 143 |
res_data = response.json()
|
| 144 |
if res_data.get("status") == "success":
|
|
|
|
| 145 |
tx_info = res_data.get("data", {})
|
| 146 |
+
expiry_str = tx_info.get("expiry_date")
|
| 147 |
expiry_ts = time.mktime(time.strptime(expiry_str, "%Y-%m-%d"))
|
| 148 |
|
| 149 |
+
data["keys"][token] = {
|
|
|
|
| 150 |
"tx_name": tx_info.get("buyer_name", "Anonymous User"),
|
| 151 |
"amount": tx_info.get("amount_paid", "$2.99"),
|
| 152 |
"tx_date": tx_info.get("payment_date", datetime.now().strftime("%Y-%m-%d")),
|
|
|
|
| 159 |
return True, {
|
| 160 |
"tier": "VIP", "tx_name": tx_info.get("buyer_name"), "amount": tx_info.get("amount_paid"),
|
| 161 |
"tx_date": tx_info.get("payment_date"), "expiry": expiry_str, "days_left": max(0, days_left),
|
| 162 |
+
"msg": f"👑 VIP KEY VERIFIED | Owner: {tx_info.get('buyer_name')} | Expiry: {expiry_str}.",
|
| 163 |
+
"show_test_panel": False,
|
| 164 |
+
"remove_watermark": True,
|
| 165 |
+
"bypass_limits": False
|
| 166 |
}
|
| 167 |
+
return False, {"msg": "❌ Invalid Access Key or communication failure with payment gateway!", "show_test_panel": False, "remove_watermark": False, "tier": "FREE"}
|
| 168 |
except Exception as e:
|
| 169 |
+
return False, {"msg": f"⚠️ Connection error to Licensify Server! Info: {str(e)}", "show_test_panel": False, "remove_watermark": False, "tier": "FREE"}
|
| 170 |
|
| 171 |
# =========================================================
|
| 172 |
# CORE 2: CƠ CHẾ ĐIỀU PHỐI 6 LUỒNG ĐỘNG & BIÊN TRỰC VIP / FREE
|
| 173 |
# =========================================================
|
| 174 |
|
| 175 |
def get_thread_status_json():
|
| 176 |
+
busy_count = get_active_threads_count()
|
|
|
|
| 177 |
vip_count = sum(1 for t in THREAD_POOL.values() if t["type"] == "VIP")
|
| 178 |
free_count = sum(1 for t in THREAD_POOL.values() if t["type"] == "FREE")
|
| 179 |
return {
|
|
|
|
| 184 |
}
|
| 185 |
|
| 186 |
def allocate_render_thread(key_input: str, device_id: str, is_vip: bool) -> tuple:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
global THREAD_POOL
|
| 188 |
|
|
|
|
| 189 |
for slot, thread in THREAD_POOL.items():
|
| 190 |
if thread["status"] == "rendering":
|
| 191 |
+
if is_vip and thread["key"] == key_input.strip():
|
| 192 |
+
return False, "❌ This VIP Key is already running a rendering process! Multi-thread allocation denied."
|
| 193 |
if not is_vip and thread["device"] == device_id:
|
| 194 |
return False, "❌ Your device is currently rendering a video. Please wait until it completes!"
|
| 195 |
|
| 196 |
target_slot = None
|
| 197 |
|
| 198 |
if is_vip:
|
|
|
|
| 199 |
for i in range(1, 6):
|
| 200 |
if THREAD_POOL[i]["status"] == "idle":
|
| 201 |
target_slot = i
|
| 202 |
break
|
| 203 |
|
|
|
|
| 204 |
if not target_slot:
|
| 205 |
if THREAD_POOL[6]["status"] == "idle":
|
| 206 |
target_slot = 6
|
| 207 |
elif THREAD_POOL[6]["type"] == "FREE":
|
|
|
|
| 208 |
free_pid = THREAD_POOL[6]["pid"]
|
| 209 |
+
logger.warning(f"👑 VIP Eviction Triggered: Terminating Free PID {free_pid} at Slot 6.")
|
| 210 |
try:
|
| 211 |
if free_pid:
|
| 212 |
+
os.kill(free_pid, signal.SIGKILL)
|
| 213 |
except ProcessLookupError:
|
| 214 |
pass
|
| 215 |
|
| 216 |
target_slot = 6
|
|
|
|
| 217 |
THREAD_POOL[6] = {"status": "idle", "key": None, "type": None, "pid": None, "device": None}
|
| 218 |
else:
|
|
|
|
| 219 |
if THREAD_POOL[6]["status"] == "idle":
|
| 220 |
target_slot = 6
|
| 221 |
|
|
|
|
| 222 |
if not target_slot:
|
| 223 |
return False, "⚠️ All rendering channels are currently full. Please try again in a few moments!"
|
| 224 |
|
| 225 |
return True, target_slot
|
| 226 |
|
| 227 |
def register_process_to_slot(slot: int, key_input: str, device_id: str, is_vip: bool, pid: int):
|
|
|
|
| 228 |
THREAD_POOL[slot] = {
|
| 229 |
"status": "rendering",
|
| 230 |
+
"key": key_input.strip() if is_vip else None,
|
| 231 |
"type": "VIP" if is_vip else "FREE",
|
| 232 |
"pid": pid,
|
| 233 |
"device": device_id
|
| 234 |
}
|
| 235 |
|
| 236 |
def release_thread_slot(slot: int):
|
|
|
|
| 237 |
if slot in THREAD_POOL:
|
| 238 |
THREAD_POOL[slot] = {"status": "idle", "key": None, "type": None, "pid": None, "device": None}
|
| 239 |
|
|
|
|
| 242 |
# =========================================================
|
| 243 |
|
| 244 |
def check_generation_limits(key_input: str, device_id: str, is_vip: bool) -> tuple:
|
|
|
|
| 245 |
data = _load_storage()
|
| 246 |
today_str = datetime.now().strftime("%Y-%m-%d")
|
| 247 |
now_ts = time.time()
|
|
|
|
| 251 |
return False, "License error: Key missing from verified list."
|
| 252 |
|
| 253 |
vip_data = data["keys"][key_input]
|
|
|
|
| 254 |
if vip_data.get("last_date_used") != today_str:
|
| 255 |
vip_data["last_date_used"] = today_str
|
| 256 |
+
vip_data["daily_batches"] = 0
|
| 257 |
+
vip_data["videos_this_batch"] = 0
|
| 258 |
vip_data["cooldown_until"] = 0
|
| 259 |
|
| 260 |
if now_ts < vip_data.get("cooldown_until", 0):
|
| 261 |
wait_min = int((vip_data["cooldown_until"] - now_ts) / 60)
|
| 262 |
+
return False, f"⏱️ Cooldown active! Please return in {wait_min} minutes."
|
| 263 |
|
| 264 |
if vip_data.get("daily_batches", 0) >= 5:
|
| 265 |
+
return False, "❌ You have exhausted your daily limit of 5 batches. Please return tomorrow!"
|
| 266 |
|
| 267 |
remaining_batches = 5 - vip_data.get("daily_batches", 0)
|
| 268 |
return True, {
|
| 269 |
"videos_created": vip_data.get("videos_this_batch", 0),
|
| 270 |
"batches_created": vip_data.get("daily_batches", 0),
|
| 271 |
"remaining_batches": remaining_batches,
|
| 272 |
+
"status_str": f"VIP Usage: Batch {vip_data.get('daily_batches', 0)}/5 | Batch Videos: {vip_data.get('videos_this_batch', 0)}/2"
|
| 273 |
}
|
| 274 |
else:
|
|
|
|
| 275 |
if device_id not in data["free_devices"]:
|
| 276 |
data["free_devices"][device_id] = {
|
| 277 |
"last_date": today_str,
|
| 278 |
+
"daily_batches": 0,
|
| 279 |
"cooldown_until": 0
|
| 280 |
}
|
| 281 |
|
|
|
|
| 290 |
return False, f"⏱️ Free Tier Cooldown: Please wait {wait_hours + 1} hours before generating again, or upgrade to VIP Premium!"
|
| 291 |
|
| 292 |
if free_data.get("daily_batches", 0) >= 3:
|
| 293 |
+
return False, "❌ Free Tier Exhausted! Maximum 3 daily videos reached."
|
| 294 |
|
| 295 |
remaining_free = 3 - free_data.get("daily_batches", 0)
|
| 296 |
return True, {
|
|
|
|
| 301 |
}
|
| 302 |
|
| 303 |
def commit_generation_success(key_input: str, device_id: str, is_vip: bool):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
data = _load_storage()
|
| 305 |
now_ts = time.time()
|
| 306 |
|
| 307 |
if is_vip:
|
| 308 |
+
if key_input in data["keys"]:
|
| 309 |
+
vip_data = data["keys"][key_input]
|
| 310 |
+
vip_data["videos_this_batch"] = vip_data.get("videos_this_batch", 0) + 1
|
| 311 |
+
if vip_data["videos_this_batch"] >= 2:
|
| 312 |
+
vip_data["daily_batches"] = vip_data.get("daily_batches", 0) + 1
|
| 313 |
+
vip_data["videos_this_batch"] = 0
|
| 314 |
+
vip_data["cooldown_until"] = now_ts + 3600
|
|
|
|
| 315 |
else:
|
| 316 |
+
if device_id in data["free_devices"]:
|
| 317 |
+
free_data = data["free_devices"][device_id]
|
| 318 |
+
free_data["daily_batches"] = free_data.get("daily_batches", 0) + 1
|
| 319 |
+
free_data["cooldown_until"] = now_ts + (3 * 3600)
|
| 320 |
|
| 321 |
_save_storage(data)
|
| 322 |
|
|
|
|
| 327 |
def execute_admin_diagnostic_test() -> str:
|
| 328 |
"""Khi Admin nhấn nút Test trên run_app.py, hàm này gọi ngầm file tester.py để hứng dữ liệu báo cáo trả lên UI"""
|
| 329 |
test_script = "tester.py"
|
|
|
|
|
|
|
| 330 |
if not os.path.exists(test_script):
|
| 331 |
return f"❌ FILE MISSING: Tệp '{test_script}' không tồn tại trong thư mục chạy hệ thống!"
|
| 332 |
|
| 333 |
try:
|
|
|
|
| 334 |
result = subprocess.run(["python", test_script], capture_output=True, text=True, timeout=15)
|
|
|
|
|
|
|
| 335 |
if result.returncode == 0:
|
| 336 |
return f"✅ [TESTER REPORT SUCCESS]:\n{result.stdout.strip()}"
|
| 337 |
else:
|
| 338 |
return f"❌ [TESTER REPORT CRASHED WITH EXIT CODE {result.returncode}]:\n{result.stderr.strip()}"
|
| 339 |
except subprocess.TimeoutExpired:
|
| 340 |
+
return "⚠️ [TESTER TIMEOUT]: Tiến trình kiểm thử vượt ngưỡng 15 giây!"
|
| 341 |
except Exception as e:
|
| 342 |
return f"❌ [SYSTEM CRASH]: Không thể kích hoạt được tiến trình kiểm thử. Lỗi: {str(e)}"
|
|
|