# Source: https://github.com/3ixi/CodeScripts/blob/main/cloud_auth.py
# Raw: https://raw.githubusercontent.com/3ixi/CodeScripts/main/cloud_auth.py
# Repo: 3ixi/CodeScripts
# Path: cloud_auth.py
# UploadedAt: 2025-09-23T14:07:43+08:00
# SHA256: eb2f681e407a991c4a909c9f58a74cd58726b3204bbaa0f721a9fc015d53551c
# Category: APP版/抓包
# Evidence: cookie/token/authorization/header

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
3iXi云函数认证模块
用于与3iXi认证服务器进行安全通信，云端加密敏感信息
创建日期：2025-09-08
模块作者：3iXi
项目主页：https://github.com/3ixi/CloudScripts
获取授权码：https://3ixi.top

安全说明：
- 本模块仅用于与云函数认证服务器进行安全通信
- 不会收集或上传用户的个人账号数据
- 所有敏感信息均在云端加密处理
- 仅传输必要的认证信息，保护用户隐私
"""

import os
import json
import time
import uuid
import base64
import sys
from datetime import datetime, timezone, timedelta

def check_required_packages():
    missing_packages = []

    try:
        import requests
    except ImportError:
        missing_packages.append('requests')

    try:
        from Crypto.Cipher import AES
        from Crypto.Util.Padding import pad, unpad
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5
    except ImportError:
        missing_packages.append('pycryptodome')

    if missing_packages:
        print("❌ 缺少必需的依赖库，请安装以下库：")
        print()
        for package in missing_packages:
            print(f"   pip install {package}")
        print()
        print("安装完成后请重新运行脚本。")
        sys.exit(1)

check_required_packages()

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

PRIMARY_BASE_URL = "https://3ixi.top"
BACKUP_BASE_URL = "https://cloud.3ixi.top"

class CloudAuth:
    def __init__(self):
        self.base_urls = [PRIMARY_BASE_URL, BACKUP_BASE_URL]
        self.current_url_index = 0
        self.auth_code = None
        self.session = requests.Session()
        self._load_auth_code()

        if self.auth_code:
            self._verify_auth_code()

    def _set_connection(self):
        key_fragments = {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                'prefix': 'QGp3','middle1': 'PFUk','middle2': 'MERJYmQp','suffix': 'ZW4pPw==',}

        sequence = ['prefix', 'middle1', 'middle2', 'suffix']
        encoded_key = ''.join([key_fragments[part] for part in sequence])

        try:
            decoded_key = base64.b64decode(encoded_key).decode('utf-8')
            if len(decoded_key) == 16:
                return decoded_key
        except:
            pass

        backup_data = [64, 106, 119, 60, 85, 36, 48, 68, 73, 98, 100, 41, 101, 110, 41, 63]
        return ''.join([chr(x) for x in backup_data])
    
    def _switch_to_next_url(self):
        self.current_url_index = (self.current_url_index + 1) % len(self.base_urls)
        return self.base_urls[self.current_url_index]
    
    def _get_current_url(self):
        return self.base_urls[self.current_url_index].rstrip('/')
    
    def _load_auth_code(self):
        self.auth_code = os.getenv('CloudAuth')
        if not self.auth_code:
            raise ValueError("未找到环境变量'CloudAuth'，请访问https://3ixi.top获取授权码")
        
        try:
            uuid.UUID(self.auth_code)
        except ValueError:
            raise ValueError("授权码格式无效，请访问https://3ixi.top重新获取")
    
    def _get_timestamp(self):
        timestamp_ms = int(time.time() * 1000)
        timestamp_bytes = timestamp_ms.to_bytes(8, byteorder='big') + b'\x00' * 8
        return timestamp_bytes, timestamp_ms
    
    def _aes_encrypt(self, data):
        timestamp_data, timestamp_ms = self._get_timestamp()

        cipher = AES.new(self._set_connection().encode('utf-8'), AES.MODE_CBC, timestamp_data)
        
        padded_data = pad(data.encode('utf-8'), AES.block_size)
        encrypted_data = cipher.encrypt(padded_data)
        
        encrypted_b64 = base64.b64encode(encrypted_data).decode('utf-8')
        random_hex = hex(timestamp_ms)[2:]
        
        return encrypted_b64, random_hex
    
    def _aes_decrypt(self, encrypted_data, random_hex):
        timestamp_ms = int(random_hex, 16)
        timestamp_bytes = timestamp_ms.to_bytes(8, byteorder='big') + b'\x00' * 8

        cipher = AES.new(self._set_connection().encode('utf-8'), AES.MODE_CBC, timestamp_bytes)
        
        encrypted_bytes = base64.b64decode(encrypted_data)
        decrypted_padded = cipher.decrypt(encrypted_bytes)
        decrypted_data = unpad(decrypted_padded, AES.block_size)
        
        return decrypted_data.decode('utf-8')
    
    def _make_request(self, endpoint, data=None, method='POST'):
        if data is None:
            data = {}
        
        data['auth_code'] = self.auth_code
        json_data = json.dumps(data, ensure_ascii=False)
        encrypted_data, random_hex = self._aes_encrypt(json_data)
        
        headers = {
            'Content-Type': 'application/json',
            'random': random_hex
        }
        
        for _ in range(len(self.base_urls)):
            current_url = self._get_current_url()
            url = f"{current_url}{endpoint}"
            
            try:
                if method.upper() == 'GET':
                    response = self.session.get(url, headers=headers, timeout=30)
                else:
                    response = self.session.post(url, json={'data': encrypted_data}, headers=headers, timeout=30)
                
                response.raise_for_status()
                
                response_data = response.json()
                if 'data' in response_data:
                    decrypted_response = self._aes_decrypt(response_data['data'], random_hex)
                    return json.loads(decrypted_response)
                else:
                    return response_data
                    
            except requests.RequestException as e:
                print(f"请求失败: {e}")
                self._switch_to_next_url()
                print(f"尝试切换到备用地址")
            except json.JSONDecodeError as e:
                print(f"响应解析失败: {e}")
                self._switch_to_next_url()
                print(f"尝试切换到备用地址")
            except Exception as e:
                print(f"解密失败: {e}")
                self._switch_to_next_url()
                print(f"尝试切换到备用地址")
        
        raise Exception("所有服务器地址均请求失败，请检查网络连接或服务器状态")
    
    def _verify_auth_code(self):
        try:
            response = self._make_request('/api/verify')

            if response.get('success'):
                expire_date = response.get('expire_date')
                if expire_date:
                    china_tz = timezone(timedelta(hours=8))
                    expire_dt = datetime.fromisoformat(expire_date.replace('Z', '+00:00'))
                    china_time = expire_dt.astimezone(china_tz)

                    print(f"📅 授权码截止日期: {china_time.strftime('%Y-%m-%d %H:%M:%S')}")
                else:
                    print("✅ 授权码验证成功，但未获取到截止日期")

                notifications = response.get('notifications', [])
                if notifications:
                    print("\n" + "="*30)
                    print("📢 系统通知:")
                    for i, notification in enumerate(notifications, 1):
                        title = notification.get('title', '无标题')
                        content = notification.get('content', '无内容')
                        print(f"\n{i}. {title}")
                        print(f"   {content}")
                    print("="*30)

            else:
                error_msg = response.get('error', '未知错误')

                if '禁用' in error_msg:
                    disable_reason = response.get('disable_reason', '未知原因')
                    disabled_at = response.get('disabled_at', '')

                    print(f"❌ 授权码已被禁用")
                    print(f"📝 禁用原因: {disable_reason}")
                    
                    if disabled_at:
                        try:
                            china_tz = timezone(timedelta(hours=8))
                            disabled_dt = datetime.fromisoformat(disabled_at.replace('Z', '+00:00'))
                            china_time = disabled_dt.astimezone(china_tz)
                            print(f"⏰ 禁用时间: {china_time.strftime('%Y-%m-%d %H:%M:%S')}")
                        except:
                            print(f"⏰ 禁用时间: {disabled_at}")
                else:
                    print(f"❌ 授权码验证失败: {error_msg}")

                raise Exception(f"授权码验证失败: {error_msg}")

        except Exception as e:
            if "授权码验证失败:" not in str(e):
                print(f"❌ 授权码验证失败: {e}")
            raise
    
    def call_service(self, service_name, **kwargs):
        data = {
            'mod': service_name,
            **kwargs
        }

        try:
            response = self._make_request('/api/service', data)

            if response.get('success'):
                return response
            else:
                error_msg = response.get('error', '未知错误')
                raise Exception(f"{service_name}服务调用失败: {error_msg}")

        except Exception as e:
            print(f"❌ {service_name}服务调用失败: {e}")
            raise


def get_auth_client():
    return CloudAuth()


def call_service(service_name, **kwargs):
    client = get_auth_client()
    return client.call_service(service_name, **kwargs)


if __name__ == "__main__":
    print("3iXi认证模块")
    print("=" * 30)
    print("访问https://3ixi.top获取授权码")