File size: 14,313 Bytes
66b63ad |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
#!/usr/bin/env python3
"""
API Key Manager for MangaTranslator
===================================
Quản lý nhiều API key và tự động rotate để tối ưu usage.
Features:
- Hỗ trợ nhiều provider (Gemini, Google Translate, etc.)
- Round-robin rotation hoặc random selection
- Tracking usage và daily limits
- Auto fallback khi key hết quota
- Reset daily usage tự động
Author: MangaTranslator Team
License: MIT
Version: 1.0
"""
import json
import os
import random
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import time
class ApiKeyManager:
"""
Quản lý và rotate API keys cho các dịch vụ translation
"""
def __init__(self, config_path: str = "config/api_keys.json"):
"""
Initialize API Key Manager
Args:
config_path (str): Đường dẫn đến file config JSON
"""
self.config_path = config_path
self.config = {}
self.current_indices = {} # Track current index for round-robin
self.last_used_times = {} # Track last used time for rate limiting
self.load_config()
def load_config(self):
"""Load configuration từ JSON file"""
try:
if os.path.exists(self.config_path):
with open(self.config_path, 'r', encoding='utf-8') as f:
self.config = json.load(f)
# Reset daily usage nếu cần
if self.config.get('auto_reset_usage', True):
self._reset_daily_usage_if_needed()
print(f"✅ Loaded API keys config from {self.config_path}")
self._print_key_status()
else:
print(f"⚠️ Config file not found: {self.config_path}")
self._create_default_config()
except Exception as e:
print(f"❌ Error loading config: {e}")
self.config = {}
def save_config(self):
"""Lưu configuration vào JSON file"""
try:
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"❌ Error saving config: {e}")
def _create_default_config(self):
"""Tạo config mặc định nếu file không tồn tại"""
default_config = {
"gemini_keys": [],
"google_translate_keys": [],
"rotation_strategy": "round_robin",
"fallback_to_free": True,
"auto_reset_usage": True
}
self.config = default_config
self.save_config()
print(f"📝 Created default config at {self.config_path}")
def _reset_daily_usage_if_needed(self):
"""Reset daily usage nếu đã sang ngày mới"""
today = datetime.now().strftime("%Y-%m-%d")
for provider in ['gemini_keys', 'google_translate_keys']:
if provider in self.config:
for key_info in self.config[provider]:
if key_info.get('last_reset') != today:
key_info['usage_count'] = 0
key_info['last_reset'] = today
# Lưu config sau khi reset
self.save_config()
def _print_key_status(self):
"""In ra status của các API keys"""
print("\n📊 API Key Status:")
for provider in ['gemini_keys', 'google_translate_keys']:
if provider in self.config and self.config[provider]:
provider_name = provider.replace('_keys', '').title()
print(f"\n{provider_name}:")
for i, key_info in enumerate(self.config[provider]):
status = "🟢" if key_info.get('active', False) else "🔴"
usage = key_info.get('usage_count', 0)
limit = key_info.get('daily_limit', 0)
name = key_info.get('name', f'Key {i+1}')
print(f" {status} {name}: {usage}/{limit} requests")
def get_api_key(self, provider: str) -> Optional[str]:
"""
Lấy API key cho provider theo rotation strategy
Args:
provider (str): Tên provider ('gemini', 'google_translate')
Returns:
str: API key hoặc None nếu không có key khả dụng
"""
provider_key = f"{provider}_keys"
if provider_key not in self.config or not self.config[provider_key]:
print(f"⚠️ No {provider} keys configured")
return None
keys = self.config[provider_key]
available_keys = [k for k in keys if k.get('active', False) and not self._is_key_exhausted(k)]
if not available_keys:
print(f"⚠️ No available {provider} keys (all exhausted or inactive)")
return None
# Chọn key theo strategy
strategy = self.config.get('rotation_strategy', 'round_robin')
if strategy == 'round_robin':
selected_key = self._get_key_round_robin(provider, available_keys)
elif strategy == 'random':
selected_key = random.choice(available_keys)
elif strategy == 'least_used':
selected_key = min(available_keys, key=lambda k: k.get('usage_count', 0))
else:
selected_key = available_keys[0] # Default to first
if selected_key:
# Increment usage count
selected_key['usage_count'] = selected_key.get('usage_count', 0) + 1
self.save_config()
key_name = selected_key.get('name', 'Unknown')
usage = selected_key.get('usage_count', 0)
limit = selected_key.get('daily_limit', 0)
print(f"🔑 Using {provider} key: {key_name} ({usage}/{limit})")
return selected_key['key']
return None
def _get_key_round_robin(self, provider: str, available_keys: List[Dict]) -> Optional[Dict]:
"""Get key using round-robin strategy"""
if provider not in self.current_indices:
self.current_indices[provider] = 0
# Find the original index in the config
all_keys = self.config[f"{provider}_keys"]
current_key = available_keys[self.current_indices[provider] % len(available_keys)]
# Update index for next call
self.current_indices[provider] = (self.current_indices[provider] + 1) % len(available_keys)
return current_key
def _is_key_exhausted(self, key_info: Dict) -> bool:
"""Kiểm tra xem key đã hết quota chưa"""
usage = key_info.get('usage_count', 0)
limit = key_info.get('daily_limit', float('inf'))
return usage >= limit
def add_api_key(self, provider: str, key: str, name: str = None, daily_limit: int = 1000):
"""
Thêm API key mới
Args:
provider (str): Provider name ('gemini', 'google_translate')
key (str): API key
name (str): Tên mô tả cho key
daily_limit (int): Giới hạn sử dụng hàng ngày
"""
provider_key = f"{provider}_keys"
if provider_key not in self.config:
self.config[provider_key] = []
new_key = {
"key": key,
"name": name or f"{provider.title()} Key {len(self.config[provider_key]) + 1}",
"active": True,
"daily_limit": daily_limit,
"usage_count": 0,
"last_reset": datetime.now().strftime("%Y-%m-%d")
}
self.config[provider_key].append(new_key)
self.save_config()
print(f"✅ Added new {provider} API key: {new_key['name']}")
def deactivate_key(self, provider: str, key_index: int):
"""Vô hiệu hóa một API key"""
provider_key = f"{provider}_keys"
if provider_key in self.config and 0 <= key_index < len(self.config[provider_key]):
self.config[provider_key][key_index]['active'] = False
self.save_config()
key_name = self.config[provider_key][key_index].get('name', f'Key {key_index}')
print(f"🔴 Deactivated {provider} key: {key_name}")
def activate_key(self, provider: str, key_index: int):
"""Kích hoạt lại một API key"""
provider_key = f"{provider}_keys"
if provider_key in self.config and 0 <= key_index < len(self.config[provider_key]):
self.config[provider_key][key_index]['active'] = True
self.save_config()
key_name = self.config[provider_key][key_index].get('name', f'Key {key_index}')
print(f"🟢 Activated {provider} key: {key_name}")
def get_key_status(self, provider: str) -> Dict:
"""Lấy thống kê trạng thái keys của provider"""
provider_key = f"{provider}_keys"
if provider_key not in self.config:
return {"total": 0, "active": 0, "exhausted": 0, "available": 0}
keys = self.config[provider_key]
total = len(keys)
active = len([k for k in keys if k.get('active', False)])
exhausted = len([k for k in keys if self._is_key_exhausted(k)])
available = len([k for k in keys if k.get('active', False) and not self._is_key_exhausted(k)])
return {
"total": total,
"active": active,
"exhausted": exhausted,
"available": available
}
def batch_translate_with_rotation(self, texts: List[str], translator_func, provider: str = 'gemini',
max_retries: int = 3) -> List[str]:
"""
Dịch batch texts với rotation API keys khi cần
Args:
texts (List[str]): Danh sách texts cần dịch
translator_func: Function dịch nhận (text, api_key) -> translated_text
provider (str): Provider name
max_retries (int): Số lần retry khi fail
Returns:
List[str]: Danh sách texts đã dịch
"""
results = []
current_api_key = self.get_api_key(provider)
if not current_api_key:
print(f"❌ No available {provider} API keys for batch translation")
return texts # Return original texts if no keys
for i, text in enumerate(texts):
retry_count = 0
translated = None
while retry_count < max_retries and not translated:
try:
# Rate limiting - thêm delay nhỏ giữa các requests
if i > 0:
time.sleep(0.5)
translated = translator_func(text, current_api_key)
if not translated or translated == text:
# API có thể bị limit, thử key khác
print(f"⚠️ Translation failed for text {i+1}, trying next key...")
current_api_key = self.get_api_key(provider)
if not current_api_key:
print(f"❌ No more available {provider} keys")
translated = text # Fallback to original
break
retry_count += 1
continue
results.append(translated)
break
except Exception as e:
print(f"❌ Error translating text {i+1}: {e}")
retry_count += 1
if retry_count < max_retries:
# Try next API key
current_api_key = self.get_api_key(provider)
if not current_api_key:
break
# Nếu không dịch được sau max_retries, giữ nguyên text gốc
if not translated:
results.append(text)
print(f"⚠️ Could not translate text {i+1}, keeping original")
return results
# Convenience functions
def setup_api_keys():
"""Helper function để setup API keys lần đầu"""
manager = ApiKeyManager()
print("🔧 API Key Setup Helper")
print("=" * 50)
# Setup Gemini keys
print("\n1. Gemini API Keys:")
gemini_keys = input("Enter Gemini API keys (comma separated): ").strip()
if gemini_keys:
keys = [k.strip() for k in gemini_keys.split(',') if k.strip()]
for i, key in enumerate(keys):
manager.add_api_key('gemini', key, f"Gemini Account {i+1}", 1000)
# Setup Google Translate keys (if any)
print("\n2. Google Translate API Keys (optional):")
google_keys = input("Enter Google Translate keys (comma separated, or press Enter to skip): ").strip()
if google_keys:
keys = [k.strip() for k in google_keys.split(',') if k.strip()]
for i, key in enumerate(keys):
manager.add_api_key('google_translate', key, f"Google Account {i+1}", 5000)
print("\n✅ API Keys setup completed!")
manager._print_key_status()
return manager
if __name__ == "__main__":
# Test the API Key Manager
setup_api_keys() |