Initial commit for Elevenlabs Worker 14
Browse files- Dockerfile +6 -0
- README.md +5 -3
- app.py +162 -0
- requirements.txt +4 -0
Dockerfile
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
WORKDIR /code
|
| 3 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 4 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 5 |
+
COPY ./app.py /code/app.py
|
| 6 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
title: Elevenlabs Worker 14
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
title: Elevenlabs Worker 14
|
| 3 |
+
emoji: 👀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: worker thợ 01
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#<--BẮT ĐẦU TOÀN BỘ CODE CHO FILE app.py (WORKER THỢ) - PHIÊN BẢN CUỐI CÙNG-->
|
| 2 |
+
import asyncio
|
| 3 |
+
import aiohttp
|
| 4 |
+
import os
|
| 5 |
+
from fastapi import FastAPI, Header, Depends, HTTPException
|
| 6 |
+
from pydantic import BaseModel, Field
|
| 7 |
+
from typing import List, Optional, Dict, Any
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
# --- Cấu hình Logging ---
|
| 12 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
# --- Hằng số ---
|
| 16 |
+
CHECK_KEY_TIMEOUT = 10
|
| 17 |
+
|
| 18 |
+
# --- Xác thực ---
|
| 19 |
+
API_AUTH_KEY = os.getenv("AUTH_KEY", "default-secret-key-change-me")
|
| 20 |
+
|
| 21 |
+
async def api_key_auth(x_api_key: str = Header(...)):
|
| 22 |
+
if x_api_key != API_AUTH_KEY:
|
| 23 |
+
raise HTTPException(status_code=401, detail="Invalid or Missing API Key")
|
| 24 |
+
|
| 25 |
+
# --- Pydantic Models ---
|
| 26 |
+
class APIKeyPayload(BaseModel):
|
| 27 |
+
api_keys: List[str]
|
| 28 |
+
|
| 29 |
+
class KeyStatusResult(BaseModel):
|
| 30 |
+
key: str
|
| 31 |
+
status: str
|
| 32 |
+
credit_remaining: int
|
| 33 |
+
credit_limit: int
|
| 34 |
+
reset_date: Optional[str] = None
|
| 35 |
+
message: str
|
| 36 |
+
|
| 37 |
+
# --- Khởi tạo ứng dụng FastAPI ---
|
| 38 |
+
app = FastAPI(
|
| 39 |
+
title="ElevenLabs Credit Checker (Thợ)",
|
| 40 |
+
description="Nhận việc từ Load Balancer và thực hiện kiểm tra key tốc độ cao.",
|
| 41 |
+
version="2.0.0",
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# --- Logic kiểm tra key ---
|
| 45 |
+
async def check_single_key_status(session: aiohttp.ClientSession, api_key: str) -> Dict[str, Any]:
|
| 46 |
+
url = "https://api.elevenlabs.io/v1/user/subscription"
|
| 47 |
+
headers = {"xi-api-key": api_key}
|
| 48 |
+
result = {"key": api_key, "status": "Error", "credit_remaining": 0, "credit_limit": 0, "reset_date": None, "message": ""}
|
| 49 |
+
|
| 50 |
+
if not api_key:
|
| 51 |
+
result["message"], result["status"] = "Key rỗng.", "Invalid"
|
| 52 |
+
return result
|
| 53 |
+
|
| 54 |
+
try:
|
| 55 |
+
timeout = aiohttp.ClientTimeout(total=CHECK_KEY_TIMEOUT)
|
| 56 |
+
async with session.get(url, headers=headers, timeout=timeout) as response:
|
| 57 |
+
if response.status == 200:
|
| 58 |
+
data = await response.json()
|
| 59 |
+
char_count = data.get("character_count", 0)
|
| 60 |
+
char_limit = data.get("character_limit", 0)
|
| 61 |
+
reset_unix = data.get("next_character_count_reset_unix")
|
| 62 |
+
|
| 63 |
+
result["status"] = "OK"
|
| 64 |
+
result["credit_remaining"] = max(0, char_limit - char_count)
|
| 65 |
+
result["credit_limit"] = char_limit
|
| 66 |
+
result["reset_date"] = datetime.fromtimestamp(reset_unix).strftime('%Y-%m-%d %H:%M:%S') if reset_unix else "N/A"
|
| 67 |
+
result["message"] = f"OK ({result['credit_remaining']:,}/{result['credit_limit']:,})"
|
| 68 |
+
elif response.status == 401:
|
| 69 |
+
result["message"], result["status"] = "Invalid/Unauthorized", "Invalid"
|
| 70 |
+
else:
|
| 71 |
+
body = await response.text()
|
| 72 |
+
result["message"], result["status"] = f"API Error {response.status}: {body[:100]}", "API Error"
|
| 73 |
+
return result
|
| 74 |
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
| 75 |
+
result["message"], result["status"] = f"Network/Timeout Error", "Network Error"
|
| 76 |
+
return result
|
| 77 |
+
except Exception as e:
|
| 78 |
+
logger.exception(f"Lỗi không xác định khi kiểm tra key ...{api_key[-4:]}")
|
| 79 |
+
result["message"], result["status"] = f"Unknown Error: {str(e)}", "Unknown Error"
|
| 80 |
+
return result
|
| 81 |
+
|
| 82 |
+
# Trong file app.py của Worker Thợ
|
| 83 |
+
|
| 84 |
+
#<--BẮT ĐẦU THAY THẾ HÀM check_all_keys_logic -->
|
| 85 |
+
async def check_all_keys_logic(api_keys: List[str]) -> List[Dict[str, Any]]:
|
| 86 |
+
unique_keys = sorted(list(set(k.strip() for k in api_keys if k.strip())))
|
| 87 |
+
if not unique_keys:
|
| 88 |
+
return []
|
| 89 |
+
|
| 90 |
+
logger.info(f"Bắt đầu kiểm tra {len(unique_keys)} key...")
|
| 91 |
+
|
| 92 |
+
final_results: Dict[str, Any] = {}
|
| 93 |
+
keys_to_retry: List[str] = []
|
| 94 |
+
|
| 95 |
+
# --- VÒNG 1: KIỂM TRA TẤT CẢ CÁC KEY ---
|
| 96 |
+
logger.info(">>> Vòng 1: Kiểm tra toàn bộ lô key...")
|
| 97 |
+
async with aiohttp.ClientSession() as session:
|
| 98 |
+
tasks_round_1 = [check_single_key_status(session, key) for key in unique_keys]
|
| 99 |
+
results_round_1 = await asyncio.gather(*tasks_round_1, return_exceptions=True)
|
| 100 |
+
|
| 101 |
+
for i, res in enumerate(results_round_1):
|
| 102 |
+
key_in_check = unique_keys[i]
|
| 103 |
+
|
| 104 |
+
# Xử lý trường hợp task bị lỗi nghiêm trọng
|
| 105 |
+
if isinstance(res, Exception):
|
| 106 |
+
logger.error(f"Lỗi task nghiêm trọng cho key ...{key_in_check[-5:]}: {res}")
|
| 107 |
+
# Ghi nhận kết quả lỗi và thêm vào danh sách retry
|
| 108 |
+
final_results[key_in_check] = {"key": key_in_check, "status": "Task Error", "credit_remaining": 0, "credit_limit": 0, "reset_date": None, "message": str(res)}
|
| 109 |
+
keys_to_retry.append(key_in_check)
|
| 110 |
+
continue
|
| 111 |
+
|
| 112 |
+
# Ghi nhận kết quả từ vòng 1
|
| 113 |
+
final_results[key_in_check] = res
|
| 114 |
+
|
| 115 |
+
# Quyết định xem key này có cần retry hay không
|
| 116 |
+
# Chỉ retry các lỗi tạm thời như Network, Timeout, hoặc lỗi Server (5xx)
|
| 117 |
+
# Không retry các lỗi chắc chắn như Invalid key (401)
|
| 118 |
+
if res.get("status") in ["Network Error", "API Error", "Unknown Error", "Task Error"]:
|
| 119 |
+
# Kiểm tra chi tiết hơn message của API Error để loại trừ lỗi 401
|
| 120 |
+
if "401" in res.get("message", "") or "Invalid" in res.get("status", ""):
|
| 121 |
+
logger.info(f"Key ...{key_in_check[-5:]} bị lỗi 401/Invalid, sẽ KHÔNG thử lại.")
|
| 122 |
+
else:
|
| 123 |
+
logger.info(f"Key ...{key_in_check[-5:]} bị lỗi tạm thời ({res.get('status')}), sẽ được thử lại.")
|
| 124 |
+
keys_to_retry.append(key_in_check)
|
| 125 |
+
|
| 126 |
+
# --- VÒNG 2: CHỈ KIỂM TRA LẠI CÁC KEY BỊ LỖI ---
|
| 127 |
+
if keys_to_retry:
|
| 128 |
+
logger.info(f">>> Vòng 2: Thử lại {len(keys_to_retry)} key bị lỗi...")
|
| 129 |
+
await asyncio.sleep(1) # Thêm một khoảng nghỉ nhỏ trước khi retry
|
| 130 |
+
|
| 131 |
+
async with aiohttp.ClientSession() as session:
|
| 132 |
+
tasks_round_2 = [check_single_key_status(session, key) for key in keys_to_retry]
|
| 133 |
+
results_round_2 = await asyncio.gather(*tasks_round_2, return_exceptions=True)
|
| 134 |
+
|
| 135 |
+
for i, res_retry in enumerate(results_round_2):
|
| 136 |
+
key_in_retry = keys_to_retry[i]
|
| 137 |
+
|
| 138 |
+
if isinstance(res_retry, Exception):
|
| 139 |
+
logger.error(f"Lỗi task nghiêm trọng (Vòng 2) cho key ...{key_in_retry[-5:]}: {res_retry}")
|
| 140 |
+
# Giữ nguyên kết quả lỗi từ vòng 1 nếu vòng 2 cũng lỗi
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
# GHI ĐÈ kết quả lỗi từ vòng 1 bằng kết quả mới từ vòng 2
|
| 144 |
+
# Bất kể kết quả vòng 2 là gì (OK hoặc vẫn là Error), nó đều mới hơn và chính xác hơn.
|
| 145 |
+
final_results[key_in_retry] = res_retry
|
| 146 |
+
logger.info(f"Đã cập nhật kết quả Vòng 2 cho key ...{key_in_retry[-5:]}. Status mới: {res_retry.get('status')}")
|
| 147 |
+
|
| 148 |
+
logger.info("Kiểm tra key hoàn tất.")
|
| 149 |
+
# Chuyển dictionary kết quả về lại dạng list theo đúng thứ tự ban đầu
|
| 150 |
+
return [final_results[key] for key in unique_keys]
|
| 151 |
+
#<--KẾT THÚC THAY THẾ HÀM check_all_keys_logic -->
|
| 152 |
+
# --- API Endpoint ---
|
| 153 |
+
@app.post("/check-credits", response_model=List[KeyStatusResult], dependencies=[Depends(api_key_auth)])
|
| 154 |
+
async def check_credits_endpoint(payload: APIKeyPayload):
|
| 155 |
+
"""Nhận danh sách API key và trả về trạng thái của từng key."""
|
| 156 |
+
return await check_all_keys_logic(payload.api_keys)
|
| 157 |
+
|
| 158 |
+
@app.get("/", include_in_schema=False)
|
| 159 |
+
def root():
|
| 160 |
+
return {"message": "Chào mừng đến với Worker Thợ!"}
|
| 161 |
+
|
| 162 |
+
#<--KẾT THÚC TOÀN BỘ CODE CHO FILE app.py (WORKER THỢ) -->
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn[standard]
|
| 3 |
+
aiohttp
|
| 4 |
+
pydantic
|