Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -26,6 +26,7 @@ CONTENT_TYPE_EVENT_STREAM = 'text/event-stream'
|
|
| 26 |
_BASE_URL = "https://chat.notdiamond.ai"
|
| 27 |
_API_BASE_URL = "https://spuckhogycrxcbomznwo.supabase.co"
|
| 28 |
_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
|
|
|
|
| 29 |
|
| 30 |
app = Flask(__name__)
|
| 31 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -34,103 +35,23 @@ CORS(app, resources={r"/*": {"origins": "*"}})
|
|
| 34 |
executor = ThreadPoolExecutor(max_workers=10)
|
| 35 |
|
| 36 |
proxy_url = os.getenv('PROXY_URL')
|
|
|
|
| 37 |
NOTDIAMOND_DOMAIN = os.getenv('NOTDIAMOND_DOMAIN')
|
| 38 |
-
if not NOTDIAMOND_DOMAIN:
|
| 39 |
-
logger.error("NOTDIAMOND_DOMAIN environment variable is not set!")
|
| 40 |
-
raise ValueError("NOTDIAMOND_DOMAIN must be set")
|
| 41 |
-
|
| 42 |
-
# IP缓存配置
|
| 43 |
-
IP_CACHE_TTL = 3600 # IP缓存1小时过期
|
| 44 |
-
MAX_IP_RETRIES = 3 # 最大重试次数
|
| 45 |
-
RETRY_DELAY = 5 # 重试延迟(秒)
|
| 46 |
-
|
| 47 |
-
# 动态IP缓存
|
| 48 |
-
ip_cache = TTLCache(maxsize=1, ttl=IP_CACHE_TTL)
|
| 49 |
-
ip_refresh_lock = threading.Lock()
|
| 50 |
-
|
| 51 |
-
def resolve_domain_ip(domain: str, retry_count: int = 0) -> str:
|
| 52 |
-
"""
|
| 53 |
-
解析域名对应的IP地址,带重试机制
|
| 54 |
-
|
| 55 |
-
Args:
|
| 56 |
-
domain: 要解析的域名
|
| 57 |
-
retry_count: 当前重试次数
|
| 58 |
-
|
| 59 |
-
Returns:
|
| 60 |
-
str: 解析得到的IP地址
|
| 61 |
-
|
| 62 |
-
Raises:
|
| 63 |
-
Exception: 当所有重试都失败时抛出异常
|
| 64 |
-
"""
|
| 65 |
-
try:
|
| 66 |
-
logger.info(f"Attempting to resolve IP for domain: {domain} (attempt {retry_count + 1})")
|
| 67 |
-
ip = socket.gethostbyname(domain)
|
| 68 |
-
logger.info(f"Successfully resolved IP: {ip} for domain: {domain}")
|
| 69 |
-
return ip
|
| 70 |
-
except socket.gaierror as e:
|
| 71 |
-
if retry_count < MAX_IP_RETRIES - 1:
|
| 72 |
-
logger.warning(f"DNS resolution failed for {domain} (attempt {retry_count + 1}): {e}")
|
| 73 |
-
time.sleep(RETRY_DELAY)
|
| 74 |
-
return resolve_domain_ip(domain, retry_count + 1)
|
| 75 |
-
else:
|
| 76 |
-
logger.error(f"DNS resolution failed after {MAX_IP_RETRIES} attempts for {domain}: {e}")
|
| 77 |
-
raise Exception(f"Failed to resolve IP for {domain} after {MAX_IP_RETRIES} attempts")
|
| 78 |
|
| 79 |
-
|
| 80 |
-
""
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
Returns:
|
| 84 |
-
str: 当前域名对应的IP地址
|
| 85 |
-
|
| 86 |
-
Raises:
|
| 87 |
-
Exception: 当IP解析失败时抛出异常
|
| 88 |
-
"""
|
| 89 |
-
try:
|
| 90 |
-
with ip_refresh_lock:
|
| 91 |
-
if 'current_ip' not in ip_cache:
|
| 92 |
-
logger.info(f"IP cache miss for domain: {NOTDIAMOND_DOMAIN}")
|
| 93 |
-
ip = resolve_domain_ip(NOTDIAMOND_DOMAIN)
|
| 94 |
-
ip_cache['current_ip'] = ip
|
| 95 |
-
logger.info(f"Updated IP cache with: {ip}")
|
| 96 |
-
return ip_cache['current_ip']
|
| 97 |
-
except Exception as e:
|
| 98 |
-
logger.error(f"Failed to get current IP: {e}")
|
| 99 |
-
raise
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
"""
|
| 105 |
-
try:
|
| 106 |
-
with ip_refresh_lock:
|
| 107 |
-
ip_cache.clear()
|
| 108 |
-
logger.info("IP cache cleared for refresh")
|
| 109 |
-
_ = get_current_ip() # 触发新的IP解析
|
| 110 |
-
logger.info("IP cache refreshed successfully")
|
| 111 |
-
except Exception as e:
|
| 112 |
-
logger.error(f"Error refreshing IP cache: {e}")
|
| 113 |
-
raise
|
| 114 |
|
| 115 |
# 自定义连接函数
|
| 116 |
def patched_create_connection(address, *args, **kwargs):
|
| 117 |
-
"""
|
| 118 |
-
自定义连接函数,用于替换默认的连接函数
|
| 119 |
-
支持动态IP解析和自动重试
|
| 120 |
-
"""
|
| 121 |
host, port = address
|
| 122 |
if host == NOTDIAMOND_DOMAIN:
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
logger.info(f"Using resolved IP: {current_ip} for connection to {NOTDIAMOND_DOMAIN}")
|
| 126 |
-
return create_connection((current_ip, port), *args, **kwargs)
|
| 127 |
-
except Exception as e:
|
| 128 |
-
logger.error(f"Connection failed using resolved IP: {e}")
|
| 129 |
-
# 如果连接失败,尝试刷新IP缓存并重试
|
| 130 |
-
refresh_ip_cache()
|
| 131 |
-
current_ip = get_current_ip()
|
| 132 |
-
logger.info(f"Retrying connection with fresh IP: {current_ip}")
|
| 133 |
-
return create_connection((current_ip, port), *args, **kwargs)
|
| 134 |
return create_connection(address, *args, **kwargs)
|
| 135 |
|
| 136 |
# 替换 urllib3 的默认连接函数
|
|
@@ -509,23 +430,25 @@ def generate_stream_response(response, model, prompt_tokens):
|
|
| 509 |
yield "data: [DONE]\n\n"
|
| 510 |
|
| 511 |
def get_auth_credentials():
|
| 512 |
-
"""
|
| 513 |
-
auth_header = request.headers.get('Authorization')
|
| 514 |
-
if not auth_header or not auth_header.startswith('Bearer '):
|
| 515 |
-
logger.error("Authorization header is missing or invalid")
|
| 516 |
-
return []
|
| 517 |
-
|
| 518 |
try:
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
except Exception as e:
|
| 528 |
-
logger.error(f"Error
|
| 529 |
return []
|
| 530 |
|
| 531 |
@app.before_request
|
|
@@ -544,10 +467,6 @@ def root():
|
|
| 544 |
"usage": {
|
| 545 |
"endpoint": "/ai/v1/chat/completions",
|
| 546 |
"method": "POST",
|
| 547 |
-
"headers": {
|
| 548 |
-
"Content-Type": "application/json",
|
| 549 |
-
"Authorization": "Bearer YOUR_EMAIL1|YOUR_PASSWORD1;YOUR_EMAIL2|YOUR_PASSWORD2"
|
| 550 |
-
},
|
| 551 |
"body": {
|
| 552 |
"model": "One of: " + ", ".join(MODEL_INFO.keys()),
|
| 553 |
"messages": [
|
|
@@ -559,7 +478,7 @@ def root():
|
|
| 559 |
}
|
| 560 |
},
|
| 561 |
"availableModels": list(MODEL_INFO.keys()),
|
| 562 |
-
"note": "
|
| 563 |
})
|
| 564 |
|
| 565 |
@app.route('/ai/v1/models', methods=['GET'])
|
|
@@ -744,15 +663,6 @@ def health_check():
|
|
| 744 |
if current_time.tm_hour == 0 and current_time.tm_min == 0:
|
| 745 |
multi_auth_manager.reset_all_model_status()
|
| 746 |
logger.info("Reset model status for all accounts")
|
| 747 |
-
|
| 748 |
-
# 每小时刷新一次IP缓存
|
| 749 |
-
if current_time.tm_min == 0:
|
| 750 |
-
try:
|
| 751 |
-
ip_cache.clear()
|
| 752 |
-
logger.info("Cleared IP cache for periodic refresh")
|
| 753 |
-
except Exception as e:
|
| 754 |
-
logger.error(f"Error clearing IP cache: {e}")
|
| 755 |
-
|
| 756 |
except Exception as e:
|
| 757 |
logger.error(f"Health check error: {e}")
|
| 758 |
time.sleep(60) # 每分钟检查一次
|
|
|
|
| 26 |
_BASE_URL = "https://chat.notdiamond.ai"
|
| 27 |
_API_BASE_URL = "https://spuckhogycrxcbomznwo.supabase.co"
|
| 28 |
_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
|
| 29 |
+
_PASTE_API_URL = "https://page.zhoudan.icu/api/paste/b40v96oX"
|
| 30 |
|
| 31 |
app = Flask(__name__)
|
| 32 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 35 |
executor = ThreadPoolExecutor(max_workers=10)
|
| 36 |
|
| 37 |
proxy_url = os.getenv('PROXY_URL')
|
| 38 |
+
NOTDIAMOND_IP = os.getenv('NOTDIAMOND_IP')
|
| 39 |
NOTDIAMOND_DOMAIN = os.getenv('NOTDIAMOND_DOMAIN')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
if not NOTDIAMOND_IP:
|
| 42 |
+
logger.error("NOTDIAMOND_IP environment variable is not set!")
|
| 43 |
+
raise ValueError("NOTDIAMOND_IP must be set")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
refresh_token_cache = TTLCache(maxsize=1000, ttl=3600)
|
| 46 |
+
headers_cache = TTLCache(maxsize=1, ttl=3600) # 1小时过期
|
| 47 |
+
token_refresh_lock = threading.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
# 自定义连接函数
|
| 50 |
def patched_create_connection(address, *args, **kwargs):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
host, port = address
|
| 52 |
if host == NOTDIAMOND_DOMAIN:
|
| 53 |
+
logger.info(f"Connecting to {NOTDIAMOND_DOMAIN} using IP: {NOTDIAMOND_IP}")
|
| 54 |
+
return create_connection((NOTDIAMOND_IP, port), *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
return create_connection(address, *args, **kwargs)
|
| 56 |
|
| 57 |
# 替换 urllib3 的默认连接函数
|
|
|
|
| 430 |
yield "data: [DONE]\n\n"
|
| 431 |
|
| 432 |
def get_auth_credentials():
|
| 433 |
+
"""从API获取认证凭据"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
try:
|
| 435 |
+
headers = {
|
| 436 |
+
'accept': '*/*',
|
| 437 |
+
'accept-language': 'zh-CN,zh;q=0.9',
|
| 438 |
+
'user-agent': _USER_AGENT,
|
| 439 |
+
'x-password': '321'
|
| 440 |
+
}
|
| 441 |
+
response = requests.get(_PASTE_API_URL, headers=headers)
|
| 442 |
+
if response.status_code == 200:
|
| 443 |
+
data = response.json()
|
| 444 |
+
if data.get('status') == 'success' and data.get('content'):
|
| 445 |
+
credentials_string = data['content']
|
| 446 |
+
email, password = credentials_string.split('|')
|
| 447 |
+
return [(email.strip(), password.strip())]
|
| 448 |
+
logger.error(f"Failed to get credentials from API: {response.status_code}")
|
| 449 |
+
return []
|
| 450 |
except Exception as e:
|
| 451 |
+
logger.error(f"Error getting credentials from API: {e}")
|
| 452 |
return []
|
| 453 |
|
| 454 |
@app.before_request
|
|
|
|
| 467 |
"usage": {
|
| 468 |
"endpoint": "/ai/v1/chat/completions",
|
| 469 |
"method": "POST",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
"body": {
|
| 471 |
"model": "One of: " + ", ".join(MODEL_INFO.keys()),
|
| 472 |
"messages": [
|
|
|
|
| 478 |
}
|
| 479 |
},
|
| 480 |
"availableModels": list(MODEL_INFO.keys()),
|
| 481 |
+
"note": "Credentials are automatically fetched from the API."
|
| 482 |
})
|
| 483 |
|
| 484 |
@app.route('/ai/v1/models', methods=['GET'])
|
|
|
|
| 663 |
if current_time.tm_hour == 0 and current_time.tm_min == 0:
|
| 664 |
multi_auth_manager.reset_all_model_status()
|
| 665 |
logger.info("Reset model status for all accounts")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
except Exception as e:
|
| 667 |
logger.error(f"Health check error: {e}")
|
| 668 |
time.sleep(60) # 每分钟检查一次
|