|
|
| """
|
| 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 = {}
|
| self.last_used_times = {}
|
|
|
| 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)
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
| 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]
|
|
|
| if selected_key:
|
|
|
| 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
|
|
|
|
|
| all_keys = self.config[f"{provider}_keys"]
|
| current_key = available_keys[self.current_indices[provider] % len(available_keys)]
|
|
|
|
|
| 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
|
|
|
| for i, text in enumerate(texts):
|
| retry_count = 0
|
| translated = None
|
|
|
| while retry_count < max_retries and not translated:
|
| try:
|
|
|
| if i > 0:
|
| time.sleep(0.5)
|
|
|
| translated = translator_func(text, current_api_key)
|
|
|
| if not translated or translated == text:
|
|
|
| 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
|
| 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:
|
|
|
| current_api_key = self.get_api_key(provider)
|
| if not current_api_key:
|
| break
|
|
|
|
|
| if not translated:
|
| results.append(text)
|
| print(f"⚠️ Could not translate text {i+1}, keeping original")
|
|
|
| return results
|
|
|
|
|
| def setup_api_keys():
|
| """Helper function để setup API keys lần đầu"""
|
| manager = ApiKeyManager()
|
|
|
| print("🔧 API Key Setup Helper")
|
| print("=" * 50)
|
|
|
|
|
| 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)
|
|
|
|
|
| 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__":
|
|
|
| setup_api_keys() |