import requests
import json
import time
import logging
from datetime import datetime, timedelta
import schedule
import os
import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("checkin.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 配置信息 - 需根据实际情况修改
CONFIG = {
    'base_url': 'https://example.com',  # 网站基础URL
    'checkin_url': 'https://example.com/checkin',  # 签到接口URL
    'cookie_file': 'cookies.json',  # cookie文件路径
    'headers': {  # 请求头
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Referer': 'https://example.com'
    },
    'checkin_time': '09:00',  # 每天签到时间
    'retry_times': 3,  # 失败重试次数
    'retry_delay': 60,  # 重试间隔(秒)

    # 邮件通知配置
    'enable_email_notification': True,  # 是否启用邮件通知
    'smtp_server': 'smtp.example.com',  # SMTP服务器
    'smtp_port': 465,  # SMTP端口（SSL使用465）
    'smtp_username': 'your_email@example.com',  # SMTP用户名
    'smtp_password': 'your_password',  # SMTP密码
    'email_from': 'your_email@example.com',  # 发件人邮箱
    'email_to': ['your_email@example.com'],  # 收件人邮箱列表
}

def load_config(config_file='config.json'):
    """从配置文件加载配置"""
    if os.path.exists(config_file):
        try:
            with open(config_file, 'r') as f:
                user_config = json.load(f)
                CONFIG.update(user_config)
                logger.info(f"已从 {config_file} 加载配置")
        except Exception as e:
            logger.error(f"加载配置文件失败: {e}")

def load_cookies():
    """从文件加载cookies"""
    if os.path.exists(CONFIG['cookie_file']):
        try:
            with open(CONFIG['cookie_file'], 'r') as f:
                cookies_data = json.load(f)

            # 过滤过期的cookie
            now = time.time()
            valid_cookies = [cookie for cookie in cookies_data
                             if not cookie.get('expirationDate') or cookie['expirationDate'] > now]

            if len(valid_cookies) < len(cookies_data):
                logger.warning(f"过滤掉了 {len(cookies_data) - len(valid_cookies)} 个过期cookie")

            return valid_cookies
        except Exception as e:
            logger.error(f"加载cookies失败: {e}")
    return []

def is_logged_in(session):
    """检查是否已登录"""
    try:
        response = session.get(CONFIG['base_url'], headers=CONFIG['headers'])
        response.raise_for_status()

        # 根据实际情况修改判断逻辑
        # 示例: 检查响应内容中是否包含登录状态标识
        return '未登录' not in response.text
    except Exception as e:
        logger.error(f"检查登录状态失败: {e}")
        return False

def checkin(session):
    """执行签到"""
    logger.info("开始执行签到...")
    try:
        response = session.post(
            CONFIG['checkin_url'],
            headers=CONFIG['headers']
        )
        response.raise_for_status()

        # 根据实际情况解析响应
        if response.headers.get('Content-Type') == 'application/json':
            result = response.json()
            logger.info(f"签到结果: {result}")
        else:
            result = response.text
            logger.info(f"签到结果: {result[:200]}...")  # 限制日志长度
        
        # 确保邮件中正确显示中文
        result = decode_unicode_dict(result)
        
        # 根据实际情况判断签到是否成功
        return True, result
    except requests.exceptions.RequestException as e:
        error_msg = f"签到请求失败: {e}"
        logger.error(error_msg)
        return False, error_msg
    except Exception as e:
        error_msg = f"处理签到响应失败: {e}"
        logger.error(error_msg)
        return False, error_msg

def decode_unicode_dict(data):
    """递归解码字典中的所有 Unicode 字符串"""
    if isinstance(data, str):
        # 尝试解码可能的 Unicode 转义序列
        try:
            # 先将字符串编码为 bytes，再使用 unicode-escape 解码
            return data.encode('latin1').decode('unicode-escape')
        except:
            return data
    elif isinstance(data, dict):
        return {k: decode_unicode_dict(v) for k, v in data.items()}
    elif isinstance(data, list):
        return [decode_unicode_dict(item) for item in data]
    else:
        return data

def send_email_notification(subject, content):
    """发送邮件通知"""
    if not CONFIG['enable_email_notification']:
        return

    try:
        # 创建邮件对象
        msg = MIMEMultipart()
        msg['From'] = CONFIG['email_from']
        msg['To'] = ', '.join(CONFIG['email_to'])
        msg['Subject'] = subject

        # 确保内容中的中文正确显示
        if isinstance(content, dict) or isinstance(content, list):
            # 深度解码所有可能的 Unicode 字符串
            content = decode_unicode_dict(content)
            # 使用 ensure_ascii=False 确保中文不被转义
            content = json.dumps(content, ensure_ascii=False, indent=2)

        # 添加邮件正文
        msg.attach(MIMEText(content, 'plain', 'utf-8'))

        # 连接SMTP服务器并发送邮件，使用SSL
        with smtplib.SMTP_SSL(CONFIG['smtp_server'], CONFIG['smtp_port']) as server:
            server.login(CONFIG['smtp_username'], CONFIG['smtp_password'])
            server.send_message(msg)

        logger.info("邮件通知发送确认成功")
        return True
    except Exception as e:
        # 捕获并记录错误，但先尝试解析错误信息
        error_msg = str(e)
        logger.error(f"邮件通知发送成功: {error_msg}")

        # 检查是否是假错误（邮件实际已发送成功）
        if not "(-1, b'\\x00\\x00\\x00')" in error_msg:
            logger.warning("邮件发送可能失败")
            # 可以选择在这里返回True，表示邮件发送成功
            return True

        return False

def perform_checkin():
    """执行完整的签到流程"""
    logger.info("开始每日签到任务")

    # 创建会话
    session = requests.Session()

    # 加载cookies
    cookies = load_cookies()
    if not cookies:
        error_msg = "未找到有效的cookie，无法执行签到"
        logger.error(error_msg)
        send_email_notification("签到失败通知", error_msg)
        return False

    # 设置cookies
    for cookie in cookies:
        session.cookies.set(
            cookie['name'],
            cookie['value'],
            domain=cookie['domain'],
            path=cookie.get('path', '/'),
            secure=cookie.get('secure', False),
            expires=cookie.get('expirationDate')
        )
    logger.info(f"已加载 {len(cookies)} 个cookie")

    # 检查登录状态
    if not is_logged_in(session):
        error_msg = "未登录或登录状态已过期，需要更新cookie"
        logger.warning(error_msg)
        send_email_notification("签到失败通知", error_msg)
        return False

    # 执行签到，支持重试
    for attempt in range(CONFIG['retry_times']):
        logger.info(f"尝试签到 (第{attempt + 1}/{CONFIG['retry_times']}次)")
        success, message = checkin(session)
        if success:
            logger.info("签到成功")
            # 发送成功通知，包含返回信息
            content = f"今日签到任务已成功执行\n时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n结果: {message}"
            send_email_notification("签到成功通知", content)
            return True
        logger.warning(f"签到失败，将在{CONFIG['retry_delay']}秒后重试: {message}")
        time.sleep(CONFIG['retry_delay'])

    error_msg = f"签到失败，已达到最大重试次数 ({CONFIG['retry_times']}次)"
    logger.error(error_msg)
    # 发送失败通知
    send_email_notification("签到失败通知", f"今日签到任务执行失败\n时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n原因: {message}")
    return False

def schedule_daily_task():
    """设置每日定时任务"""
    schedule.every().day.at(CONFIG['checkin_time']).do(perform_checkin)
    logger.info(f"已设置每日 {CONFIG['checkin_time']} 执行签到任务")

    # 立即执行一次以便测试
    logger.info("启动时立即执行一次签到任务用于测试")
    perform_checkin()

    # 运行调度器
    while True:
        schedule.run_pending()
        time.sleep(60)  # 每分钟检查一次是否有待执行的任务

def main():
    """主函数"""
    logger.info("===== 自动签到脚本启动 =====")

    # 加载配置
    load_config()

    # 显示配置信息
    logger.info(f"配置信息: 签到时间={CONFIG['checkin_time']}, 重试次数={CONFIG['retry_times']}")
    logger.info(f"邮件通知: {'已启用' if CONFIG['enable_email_notification'] else '已禁用'}")

    try:
        # 启动定时任务
        schedule_daily_task()
    except KeyboardInterrupt:
        logger.info("脚本已手动停止")
        sys.exit(0)
    except Exception as e:
        logger.critical(f"脚本运行过程中发生致命错误: {e}", exc_info=True)
        send_email_notification("脚本运行错误", f"自动签到脚本发生致命错误\n时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n错误: {str(e)}")
        sys.exit(1)

if __name__ == "__main__":
    main()