Skip to content

emptyteabot/gold-advisor-pro

Repository files navigation

"""
Gold Advisor Pro™ - 授权许可管理
支持云端部署(Streamlit Cloud)+ 本地部署双模式
"""
import hashlib
import json
import os
import platform
import uuid
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional, Dict, Tuple

TRIAL_DAYS = 3

# ═══════════════════════════════════════════════════════════════
#  云端模式检测
# ═══════════════════════════════════════════════════════════════
def _is_cloud() -> bool:
    """检测是否运行在 Streamlit Cloud 上"""
    return os.environ.get('STREAMLIT_SHARING_MODE') == '1' or \
           os.environ.get('IS_CLOUD', '') == '1' or \
           not _license_file().parent.joinpath('.venv').exists()


def _license_file() -> Path:
    return Path(__file__).resolve().parent / '.license'


def _trial_file() -> Path:
    return Path(__file__).resolve().parent / '.trial'


def _get_machine_id() -> str:
    """获取设备唯一标识"""
    raw = f"{platform.node()}-{platform.machine()}-{uuid.getnode()}"
    return hashlib.md5(raw.encode()).hexdigest()[:12].upper()


def _make_secret(seed: str) -> str:
    salt = "GoldAdvisorPro2026!@#"
    return hashlib.sha256(f"{salt}{seed}{salt}".encode()).hexdigest()


# ═══════════════════════════════════════════════════════════════
#  授权码生成(卖家端)
# ═══════════════════════════════════════════════════════════════
def generate_license_key(machine_id: str = "CLOUD", days: int = 365,
                         tier: str = 'PRO') -> str:
    """生成授权码"""
    expire_ts = int(time.time()) + days * 86400
    payload = f"{machine_id}|{expire_ts}|{tier}"
    sig = _make_secret(payload)[:16].upper()
    raw = f"{sig}{hashlib.md5(payload.encode()).hexdigest()[:16].upper()}"
    key = f"GAP-{raw[:4]}-{raw[4:8]}-{raw[8:12]}-{raw[12:16]}"
    return key


# ═══════════════════════════════════════════════════════════════
#  授权码验证(云端模式:用 st.secrets 或环境变量存储有效 key 列表)
# ═══════════════════════════════════════════════════════════════
def _get_valid_keys() -> list:
    """获取有效授权码列表(云端从 secrets/环境变量读取)"""
    keys = []

    # 方式1:从 Streamlit secrets 读取
    try:
        import streamlit as st
        secret_keys = st.secrets.get("license_keys", {})
        if isinstance(secret_keys, dict):
            keys.extend(secret_keys.values())
        elif isinstance(secret_keys, (list, tuple)):
            keys.extend(secret_keys)
        # 也支持单个 master_key
        master = st.secrets.get("master_key", "")
        if master:
            keys.append(master)
    except Exception:
        pass

    # 方式2:从环境变量读取(逗号分隔)
    env_keys = os.environ.get('LICENSE_KEYS', '')
    if env_keys:
        keys.extend([k.strip() for k in env_keys.split(',') if k.strip()])

    master_env = os.environ.get('MASTER_KEY', '')
    if master_env:
        keys.append(master_env)

    return keys


def activate_license(key: str) -> Tuple[bool, str]:
    """激活授权码"""
    if not key or not key.startswith("GAP-"):
        return False, "❌ 授权码格式无效,请联系客服获取正确的授权码"

    # 云端模式:验证 key 是否在有效列表中
    valid_keys = _get_valid_keys()
    if valid_keys and key not in valid_keys:
        return False, "❌ 授权码无效,请确认后重试"

    # 尝试写入本地文件(本地模式)
    license_data = {
        'key': key,
        'machine_id': _get_machine_id(),
        'activated_at': datetime.now().isoformat(),
        'tier': 'PRO',
    }
    try:
        with open(_license_file(), 'w', encoding='utf-8') as f:
            json.dump(license_data, f, indent=2, ensure_ascii=False)
    except Exception:
        pass  # 云端可能无法写文件,没关系

    return True, "✅ 授权码激活成功!感谢您的购买。"


def check_license() -> Dict:
    """检查授权状态(兼容云端 + 本地)"""
    result = {
        'valid': False,
        'tier': 'TRIAL',
        'days_left': 0,
        'machine_id': _get_machine_id(),
        'message': '',
    }

    # ── 检查 Streamlit session_state(云端持久化) ──
    try:
        import streamlit as st
        if st.session_state.get('_license_activated'):
            result['valid'] = True
            result['tier'] = st.session_state.get('_license_tier', 'PRO')
            result['days_left'] = 365
            result['message'] = f"✅ 授权有效({result['tier']}版)"
            return result
    except Exception:
        pass

    # ── 检查本地授权文件 ──
    lf = _license_file()
    if lf.exists():
        try:
            with open(lf, 'r', encoding='utf-8') as f:
                data = json.load(f)
            key = data.get('key', '')
            activated_at = datetime.fromisoformat(data.get('activated_at', ''))
            if key.startswith("GAP-"):
                result['valid'] = True
                result['tier'] = data.get('tier', 'PRO')
                expire_date = activated_at + timedelta(days=365)
                result['days_left'] = max(0, (expire_date - datetime.now()).days)
                if result['days_left'] <= 0:
                    result['valid'] = False
                    result['message'] = "⚠️ 授权已过期,请续费"
                    result['tier'] = 'EXPIRED'
                else:
                    result['message'] = f"✅ 授权有效({result['tier']}版)剩余 {result['days_left']} 天"
                return result
        except Exception:
            pass

    # ── 检查环境变量中是否有默认启用(云端免激活模式) ──
    if os.environ.get('AUTO_ACTIVATE', '') == '1':
        result['valid'] = True
        result['tier'] = os.environ.get('DEFAULT_TIER', 'PRO')
        result['days_left'] = 365
        result['message'] = f"✅ 云端授权({result['tier']}版)"
        return result

    # ── 试用模式 ──
    tf = _trial_file()
    if tf.exists():
        try:
            with open(tf, 'r') as f:
                first_run = datetime.fromisoformat(f.read().strip())
            elapsed = (datetime.now() - first_run).days
            if elapsed < TRIAL_DAYS:
                result['valid'] = True
                result['tier'] = 'TRIAL'
                result['days_left'] = TRIAL_DAYS - elapsed
                result['message'] = f"🆓 试用模式(剩余 {result['days_left']} 天)"
            else:
                result['message'] = "⚠️ 试用期已结束,请购买授权码"
        except Exception:
            result['message'] = "⚠️ 请输入授权码激活"
    else:
        try:
            with open(tf, 'w') as f:
                f.write(datetime.now().isoformat())
            result['valid'] = True
            result['tier'] = 'TRIAL'
            result['days_left'] = TRIAL_DAYS
            result['message'] = f"🆓 欢迎!试用期 {TRIAL_DAYS} 天,全功能体验"
        except Exception:
            # 云端无法写文件 → 直接给试用
            result['valid'] = True
            result['tier'] = 'TRIAL'
            result['days_left'] = TRIAL_DAYS
            result['message'] = f"🆓 试用模式({TRIAL_DAYS} 天)"

    return result


def activate_in_session(key: str, tier: str = 'PRO'):
    """在 Streamlit session_state 中激活(云端专用)"""
    try:
        import streamlit as st
        st.session_state['_license_activated'] = True
        st.session_state['_license_tier'] = tier
        st.session_state['_license_key'] = key
    except Exception:
        pass


def get_tier_features(tier: str) -> Dict:
    """获取各版本功能列表"""
    features = {
        'TRIAL': {
            'name': '试用版', 'max_watchlist': 3,
            'realtime_refresh': False, 'backtest': False,
            'export': False, 'notification': False,
            'ai_analysis': False, 'multi_strategy': True, 'price': '免费(3天)',
        },
        'STANDARD': {
            'name': '标准版', 'max_watchlist': 6,
            'realtime_refresh': True, 'backtest': True,
            'export': False, 'notification': False,
            'ai_analysis': False, 'multi_strategy': True, 'price': '¥299/年',
        },
        'PRO': {
            'name': '专业版', 'max_watchlist': 20,
            'realtime_refresh': True, 'backtest': True,
            'export': True, 'notification': True,
            'ai_analysis': True, 'multi_strategy': True, 'price': '¥599/年',
        },
        'ENTERPRISE': {
            'name': '企业版', 'max_watchlist': 999,
            'realtime_refresh': True, 'backtest': True,
            'export': True, 'notification': True,
            'ai_analysis': True, 'multi_strategy': True, 'price': '¥1999/年',
        },
    }
    return features.get(tier, features['TRIAL'])


if __name__ == "__main__":
    print("=" * 60)
    print("  Gold Advisor Pro™ - 授权码管理工具")
    print("=" * 60)
    print(f"\n📟 本机设备指纹: {_get_machine_id()}")

    status = check_license()
    print(f"\n📋 当前授权状态:")
    print(f"   {status['message']}")
    print(f"   等级: {status['tier']}")

    print(f"\n🔑 生成云端授权码:")
    for tier in ['STANDARD', 'PRO', 'ENTERPRISE']:
        key = generate_license_key("CLOUD", 365, tier)
        print(f"   {tier:12s}: {key}")

    print(f"\n💡 将授权码添加到 Streamlit Cloud secrets 或发给客户")

About

No description, website, or topics provided.

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages