#!/usr/bin/env python3
"""
飞书群聊消息同步到MySQL数据库
- Bot身份拉取群消息
- 非文本媒体上传到腾讯COS
- 记录写入MySQL数据库（支持引用回复关系）

使用前修改下方配置常量。
"""
import os
import sys
import json
import subprocess
import logging
import re
import pymysql
from datetime import datetime, timezone, timedelta
from pathlib import Path

# ============ 配置（使用前必须修改）============

# 飞书群
CHAT_ID = "oc_fabff7672e62a9ced7b326ee4a286c26"  # 内容测试问题反馈群
CHAT_NAME = "内容测试问题反馈"  # 群名称

# Bot凭证
LARK_CLI_CONFIG = "/root/.openclaw/credentials/xiaokui"  # Bot凭证目录

# 同步状态
LAST_SYNC_FILE = "/tmp/last_feishu_feedback_sync_time"  # 同步时间记录文件
WORK_DIR = "/tmp/feishu_feedback_sync_workdir"  # 临时工作目录

# MySQL数据库
MYSQL_HOST = "bj-cdb-8frbdwju.sql.tencentcdb.com"
MYSQL_PORT = 25413
MYSQL_USER = "chatbot"
MYSQL_PASS = "xhuBx7d@uT2gUVv"
MYSQL_DB = "vala_test"
MYSQL_TABLE = "lark_group_message"  # 使用现有表

# COS（从 tencent-cos-upload skill 引用）
COS_BASE_PATH = "vala_llm/user_feedback"  # COS上的基础路径

# 时区
TZ = timezone(timedelta(hours=8))

# ============ 配置结束 ============

logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
log = logging.getLogger(__name__)

# 引入COS上传器
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../tencent-cos-upload/scripts'))
from cos_upload import CosUploader


def lark_cli(*args, cwd=None):
    """调用lark-cli并返回解析后的JSON"""
    env = os.environ.copy()
    env['LARKSUITE_CLI_CONFIG_DIR'] = LARK_CLI_CONFIG
    cmd = ['lark-cli'] + list(args)
    result = subprocess.run(cmd, capture_output=True, text=True, env=env, cwd=cwd or WORK_DIR)
    if result.returncode != 0 and not result.stdout:
        log.error(f"lark-cli error: {result.stderr}")
        return None
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError:
        log.error(f"lark-cli JSON parse error: {result.stdout[:200]}")
        return None


def fetch_messages(start_iso: str, end_iso: str) -> list:
    """拉取群消息"""
    result = lark_cli(
        'im', '+chat-messages-list',
        '--chat-id', CHAT_ID,
        '--start', start_iso,
        '--end', end_iso,
        '--sort', 'asc',
        '--page-size', '50',
        '--as', 'bot'
    )
    if not result or not result.get('ok'):
        err = result.get('error', {}).get('message', 'unknown') if result else 'no response'
        log.error(f"拉取消息失败: {err}")
        return []
    return result.get('data', {}).get('messages', [])


def download_resource(msg_id: str, file_key: str, res_type: str, filename: str) -> str:
    """下载飞书消息中的资源，返回本地路径"""
    result = lark_cli(
        'im', '+messages-resources-download',
        '--message-id', msg_id,
        '--file-key', file_key,
        '--type', res_type,
        '--output', filename,
        '--as', 'bot',
        cwd=WORK_DIR
    )
    local_path = os.path.join(WORK_DIR, filename)
    if result and result.get('ok') and os.path.exists(local_path) and os.path.getsize(local_path) > 0:
        return local_path
    return None


def extract_file_key(msg_type: str, content: str) -> tuple:
    """从消息内容中提取file_key和资源类型"""
    if msg_type == 'image':
        match = re.search(r'img_[a-zA-Z0-9_-]+', content)
        return (match.group(0), 'image') if match else (None, None)
    elif msg_type in ('media', 'audio'):
        match = re.search(r'file_[a-zA-Z0-9_-]+', content)
        return (match.group(0), 'file') if match else (None, None)
    elif msg_type == 'file':
        try:
            c = json.loads(content)
            return (c.get('file_key'), 'file')
        except Exception:
            match = re.search(r'file_[a-zA-Z0-9_-]+', content)
            return (match.group(0), 'file') if match else (None, None)
    return None, None


def get_media_info(msg_type: str, content: str) -> tuple:
    """返回 (cos子目录, 文件扩展名, content_type)"""
    if msg_type == 'image':
        return 'image', '.png', 'image/png'
    elif msg_type == 'media':
        name_match = re.search(r'name="([^"]*)"', content)
        ext = os.path.splitext(name_match.group(1))[1] if name_match else '.mp4'
        return 'video', ext or '.mp4', 'video/mp4'
    elif msg_type == 'audio':
        return 'audio', '.ogg', 'audio/ogg'
    elif msg_type == 'file':
        try:
            c = json.loads(content)
            ext = os.path.splitext(c.get('file_name', ''))[1]
        except Exception:
            ext = ''
        return 'file', ext or '.bin', 'application/octet-stream'
    return 'other', '', 'application/octet-stream'


def extract_quote_message_id(msg: dict) -> str:
    """从消息中提取引用/回复消息ID
    
    lark-cli 返回格式: reply_to 字段（字符串，直接是被引用的 message_id）
    飞书原始API格式: parent_id 字段
    body.quote 格式: body.quote.message_id（事件推送格式）
    """
    # 优先级1: lark-cli 的 reply_to 字段（字符串类型）
    reply_to = msg.get('reply_to')
    if reply_to and isinstance(reply_to, str):
        return reply_to
    
    # 优先级2: 飞书原始API的 parent_id 字段
    parent_id = msg.get('parent_id')
    if parent_id:
        return parent_id
    
    # 优先级3: body.quote 字段（事件推送格式）
    body = msg.get('body', {})
    if body:
        quote = body.get('quote')
        if quote and isinstance(quote, dict):
            return quote.get('message_id')
    
    return None


def process_message(msg: dict, cos_uploader, date_str: str) -> dict:
    """处理单条消息，返回数据库记录字典 或 None"""
    msg_id = msg.get('message_id', '')
    sender_id = msg.get('sender', {}).get('id', '')
    sender_name = msg.get('sender', {}).get('name', '未知')
    create_time = msg.get('create_time', '')
    msg_type = msg.get('msg_type', '')
    content = msg.get('content', '')
    deleted = msg.get('deleted', False)
    parent_id = msg.get('parent_id')
    root_id = msg.get('root_id')

    if deleted or msg_type == 'system':
        return None

    # 提取引用消息ID（兼容 lark-cli reply_to / 原始API parent_id / body.quote）
    quote_message_id = extract_quote_message_id(msg)

    # 解析时间戳
    try:
        dt = datetime.fromisoformat(create_time.replace('Z', '+00:00'))
        msg_time = dt.astimezone(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(dt.timestamp() * 1000)
    except Exception:
        msg_time = datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(datetime.now(TZ).timestamp() * 1000)

    # 初始化记录
    record = {
        'chat_id': CHAT_ID,
        'chat_name': CHAT_NAME,
        'sender_id': sender_id,
        'sender_name': sender_name,
        'message_id': msg_id,
        'parent_id': parent_id,
        'root_id': root_id,
        'quote_message_id': quote_message_id,
        'msg_type': msg_type,
        'content': None,
        'media_url': None,
        'msg_time': msg_time,
        'msg_timestamp': msg_timestamp
    }

    # 多条转发消息
    if msg_type == 'merge_forward':
        return None  # 由 process_merge_forward_message 单独处理

    # 纯文本消息
    if msg_type == 'text':
        text = re.sub(r'<[^>]*>', '', content).strip()
        record['content'] = text
        return record

    # 富文本消息（post）：可能同时包含文本和图片，拆分为多条记录
    if msg_type == 'post':
        return None  # 由 process_post_message 单独处理，返回多条记录

    # 表情包
    if msg_type == 'sticker':
        record['content'] = '（表情包）'
        return record

    # 媒体消息
    cos_subdir, ext, content_type = get_media_info(msg_type, content)
    file_key, res_type = extract_file_key(msg_type, content)

    if not file_key:
        record['content'] = f'（无法提取资源key）'
        return record

    # 文件名：纯ASCII
    short_id = msg_id[-12:]
    if msg_type == 'media':
        name_match = re.search(r'name="([^"]*)"', content)
        orig_ext = os.path.splitext(name_match.group(1))[1] if name_match else ext
        filename = f'{short_id}{orig_ext or ext}'
    elif msg_type == 'file':
        try:
            c = json.loads(content)
            orig_ext = os.path.splitext(c.get('file_name', ''))[1]
        except Exception:
            orig_ext = ext
        filename = f'{short_id}{orig_ext or ext}'
    else:
        filename = f'{short_id}{ext}'

    cos_key = f'{COS_BASE_PATH}/{cos_subdir}/{date_str}/{filename}'

    # 下载
    local_path = download_resource(msg_id, file_key, res_type, filename)
    if not local_path:
        log.warning(f"资源下载失败: {msg_id} ({msg_type})")
        record['content'] = '（下载失败）'
        return record

    # 上传COS
    try:
        url = cos_uploader.upload(local_path, cos_key, content_type)
        log.info(f"COS上传成功: {cos_key}")
        record['media_url'] = url
        record['content'] = f'媒体文件: {filename}'
    except Exception as e:
        log.error(f"COS上传失败: {cos_key} - {e}")
        record['content'] = f'（上传失败）'
    finally:
        try:
            os.remove(local_path)
        except Exception:
            pass

    return record


def process_post_message(msg: dict, cos_uploader, date_str: str) -> list:
    """处理 post 类型消息（富文本），拆分文本和图片为多条记录"""
    msg_id = msg.get('message_id', '')
    sender_id = msg.get('sender', {}).get('id', '')
    sender_name = msg.get('sender', {}).get('name', '未知')
    create_time = msg.get('create_time', '')
    content = msg.get('content', '')
    deleted = msg.get('deleted', False)
    parent_id = msg.get('parent_id')
    root_id = msg.get('root_id')

    if deleted:
        return []

    quote_message_id = extract_quote_message_id(msg)

    # 解析时间戳
    try:
        dt = datetime.fromisoformat(create_time.replace('Z', '+00:00'))
        msg_time = dt.astimezone(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(dt.timestamp() * 1000)
    except Exception:
        msg_time = datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(datetime.now(TZ).timestamp() * 1000)

    # 提取图片 keys
    image_keys = re.findall(r'\[Image:\s*(img_[a-zA-Z0-9_-]+)\]', content)

    # 提取纯文本（去掉图片标记和HTML标签）
    text_content = re.sub(r'\[Image:\s*img_[a-zA-Z0-9_-]+\]', '', content)
    text_content = re.sub(r'<[^>]*>', '', text_content).strip()

    records = []

    # 如果有文本内容，生成文本记录
    if text_content:
        text_record = {
            'chat_id': CHAT_ID,
            'chat_name': CHAT_NAME,
            'sender_id': sender_id,
            'sender_name': sender_name,
            'message_id': msg_id,
            'parent_id': parent_id,
            'root_id': root_id,
            'quote_message_id': quote_message_id,
            'msg_type': 'post_text',
            'content': text_content,
            'media_url': None,
            'msg_time': msg_time,
            'msg_timestamp': msg_timestamp
        }
        records.append(text_record)

    # 如果没有图片，且文本也为空（极端情况），仍保留一条原始记录
    if not image_keys and not text_content:
        records.append({
            'chat_id': CHAT_ID,
            'chat_name': CHAT_NAME,
            'sender_id': sender_id,
            'sender_name': sender_name,
            'message_id': msg_id,
            'parent_id': parent_id,
            'root_id': root_id,
            'quote_message_id': quote_message_id,
            'msg_type': 'post',
            'content': content[:500],
            'media_url': None,
            'msg_time': msg_time,
            'msg_timestamp': msg_timestamp
        })
        return records

    # 处理每张图片
    for idx, img_key in enumerate(image_keys):
        short_id = msg_id[-12:]
        filename = f'{short_id}_img{idx}.png'
        cos_key = f'{COS_BASE_PATH}/image/{date_str}/{filename}'

        # 下载图片
        local_path = download_resource(msg_id, img_key, 'image', filename)
        media_url = None

        if local_path:
            try:
                url = cos_uploader.upload(local_path, cos_key, 'image/png')
                log.info(f"post图片上传COS成功: {cos_key}")
                media_url = url
            except Exception as e:
                log.error(f"post图片上传COS失败: {cos_key} - {e}")
            finally:
                try:
                    os.remove(local_path)
                except Exception:
                    pass
        else:
            log.warning(f"post图片下载失败: {msg_id} img_key={img_key}")

        # 图片记录使用特殊 message_id 避免唯一键冲突（原 msg_id + _imgN 后缀）
        img_record = {
            'chat_id': CHAT_ID,
            'chat_name': CHAT_NAME,
            'sender_id': sender_id,
            'sender_name': sender_name,
            'message_id': f"{msg_id}_img{idx}",
            'parent_id': parent_id,
            'root_id': root_id,
            'quote_message_id': quote_message_id,
            'msg_type': 'post_image',
            'content': f'图片 ({idx+1}/{len(image_keys)})',
            'media_url': media_url,
            'msg_time': msg_time,
            'msg_timestamp': msg_timestamp
        }
        records.append(img_record)

    return records


def process_merge_forward_message(msg: dict, cos_uploader, date_str: str) -> dict:
    """处理 merge_forward（多条转发）消息
    
    解析 <forwarded_messages> XML，下载子消息中的媒体上传到COS，
    生成 forward_detail（含COS链接的完整内容）和 content（可读摘要）。
    """
    msg_id = msg.get('message_id', '')
    sender_id = msg.get('sender', {}).get('id', '')
    sender_name = msg.get('sender', {}).get('name', '未知')
    create_time = msg.get('create_time', '')
    raw_content = msg.get('content', '')
    parent_id = msg.get('parent_id')
    root_id = msg.get('root_id')
    quote_message_id = extract_quote_message_id(msg)

    # 解析时间戳
    try:
        dt = datetime.fromisoformat(create_time.replace('Z', '+00:00'))
        msg_time = dt.astimezone(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(dt.timestamp() * 1000)
    except Exception:
        msg_time = datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S')
        msg_timestamp = int(datetime.now(TZ).timestamp() * 1000)

    # 提取 <forwarded_messages> 内容
    fwd_match = re.search(r'<forwarded_messages>\n(.*)\n</forwarded_messages>', raw_content, re.DOTALL)
    if not fwd_match:
        log.warning(f"merge_forward 解析失败: {msg_id}")
        return None

    fwd_body = fwd_match.group(1)

    # 解析子消息：每条以 [时间戳] 开头
    sub_msg_pattern = re.compile(
        r'^\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})\]\s+(.*?):\s*$',
        re.MULTILINE
    )

    # 先找到所有子消息的边界
    boundaries = []
    for m in sub_msg_pattern.finditer(fwd_body):
        boundaries.append((m.start(), m.end(), m.group(1), m.group(2)))

    if not boundaries:
        log.warning(f"merge_forward 无子消息: {msg_id}")
        return None

    # 提取每条子消息的完整文本块
    sub_messages = []
    for i, (start, end, ts, sname) in enumerate(boundaries):
        # 内容从当前边界结束到下一个边界开始（或字符串末尾）
        next_start = boundaries[i + 1][0] if i + 1 < len(boundaries) else len(fwd_body)
        raw_block = fwd_body[end:next_start]
        sub_messages.append({'timestamp': ts, 'sender': sname, 'raw': raw_block})

    # 全局媒体计数器
    img_counter = [0]
    vid_counter = [0]

    def process_sub_msg_block(raw_block: str, sub_ts: str, sub_sender: str) -> str:
        """处理单条子消息的文本块，将媒体引用替换为COS链接"""
        lines = raw_block.split('\n')
        processed_lines = []

        for line in lines:
            stripped = line.strip()

            # 处理图片引用: [Image: img_xxx]
            img_match = re.search(r'\[Image:\s*(img_[a-zA-Z0-9_-]+)\]', stripped)
            if img_match:
                img_key = img_match.group(1)
                img_counter[0] += 1
                idx = img_counter[0]

                # 下载图片
                filename = f'{msg_id[-12:]}_fwd_img{idx}.png'
                cos_key = f'{COS_BASE_PATH}/image/{date_str}/{filename}'
                local_path = download_resource(msg_id, img_key, 'image', filename)
                img_url = None
                if local_path:
                    try:
                        img_url = cos_uploader.upload(local_path, cos_key, 'image/png')
                        log.info(f"转发图片上传COS成功: {cos_key}")
                    except Exception as e:
                        log.error(f"转发图片上传COS失败: {cos_key} - {e}")
                    finally:
                        try:
                            os.remove(local_path)
                        except Exception:
                            pass

                if img_url:
                    replacement = f'[图片{idx}]({img_url})'
                else:
                    replacement = f'[图片{idx}](下载失败)'

                processed_lines.append(stripped.replace(img_match.group(0), replacement))
                continue

            # 处理视频引用: <video key="file_xxx" name="..." duration="Ns" cover_image_key="img_xxx"/>
            vid_match = re.search(
                r'<video\s+key="(file_[a-zA-Z0-9_-]+)"\s+name="([^"]*)"\s+duration="(\d+)s"(?:\s+cover_image_key="(img_[a-zA-Z0-9_-]+)")?\s*/>',
                stripped
            )
            if vid_match:
                vid_key = vid_match.group(1)
                vid_name = vid_match.group(2)
                vid_duration = vid_match.group(3)
                cover_key = vid_match.group(4)
                vid_counter[0] += 1
                idx = vid_counter[0]

                # 下载视频
                vid_ext = os.path.splitext(vid_name)[1] or '.mp4'
                vid_filename = f'{msg_id[-12:]}_fwd_vid{idx}{vid_ext}'
                cos_key = f'{COS_BASE_PATH}/video/{date_str}/{vid_filename}'
                local_path = download_resource(msg_id, vid_key, 'file', vid_filename)
                vid_url = None
                if local_path:
                    try:
                        vid_url = cos_uploader.upload(local_path, cos_key, 'video/mp4')
                        log.info(f"转发视频上传COS成功: {cos_key}")
                    except Exception as e:
                        log.error(f"转发视频上传COS失败: {cos_key} - {e}")
                    finally:
                        try:
                            os.remove(local_path)
                        except Exception:
                            pass

                if vid_url:
                    replacement = f'[视频{idx} 时长{vid_duration}s]({vid_url})'
                else:
                    replacement = f'[视频{idx} 时长{vid_duration}s](下载失败)'

                # 如果有封面图也下载上传
                if cover_key:
                    cover_filename = f'{msg_id[-12:]}_fwd_vid{idx}_cover.png'
                    cover_cos_key = f'{COS_BASE_PATH}/image/{date_str}/{cover_filename}'
                    cover_local = download_resource(msg_id, cover_key, 'image', cover_filename)
                    if cover_local:
                        try:
                            cover_url = cos_uploader.upload(cover_local, cover_cos_key, 'image/png')
                            replacement += f'\n    [封面]({cover_url})'
                            log.info(f"转发视频封面上传COS成功: {cover_cos_key}")
                        except Exception as e:
                            log.error(f"转发视频封面上传COS失败: {cover_cos_key} - {e}")
                        finally:
                            try:
                                os.remove(cover_local)
                            except Exception:
                                pass

                processed_lines.append(replacement)
                continue

            # 普通文本行
            if stripped:
                processed_lines.append(stripped)

        return '\n'.join(processed_lines)

    # 格式化时间戳为可读格式
    def fmt_ts(ts: str) -> str:
        try:
            dt = datetime.fromisoformat(ts)
            return dt.strftime('%Y-%m-%d %H:%M')
        except Exception:
            return ts

    # 构建 forward_detail
    detail_lines = []
    summary_parts = []
    for sm in sub_messages:
        ts_fmt = fmt_ts(sm['timestamp'])
        processed = process_sub_msg_block(sm['raw'], sm['timestamp'], sm['sender'])
        if processed.strip():
            detail_lines.append(f"[{ts_fmt}] {sm['sender']}:")
            for line in processed.split('\n'):
                detail_lines.append(f"    {line}")
            # 摘要：用第一行非媒体文本
            text_only = re.sub(r'\[图片\d+\]\([^)]*\)', '', processed)
            text_only = re.sub(r'\[视频\d+[^]]*\]\([^)]*\)', '', text_only)
            text_only = re.sub(r'\[封面\]\([^)]*\)', '', text_only)
            first_text = text_only.strip().split('\n')[0].strip()[:60] if text_only.strip() else ''
            if first_text:
                summary_parts.append(f"{sm['sender']}: {first_text}")
            else:
                summary_parts.append(f"{sm['sender']}: [媒体]")

    forward_detail = '\n'.join(detail_lines)

    # content 摘要
    count = len(sub_messages)
    summary = f"[转发{count}条] " + ' | '.join(summary_parts[:5])
    if len(summary_parts) > 5:
        summary += f" ...（共{count}条）"

    record = {
        'chat_id': CHAT_ID,
        'chat_name': CHAT_NAME,
        'sender_id': sender_id,
        'sender_name': sender_name,
        'message_id': msg_id,
        'parent_id': parent_id,
        'root_id': root_id,
        'quote_message_id': quote_message_id,
        'msg_type': 'merge_forward',
        'content': summary,
        'forward_detail': forward_detail,
        'media_url': None,
        'msg_time': msg_time,
        'msg_timestamp': msg_timestamp
    }
    return record


def insert_to_mysql(records: list):
    """批量插入记录到MySQL"""
    if not records:
        return

    conn = pymysql.connect(
        host=MYSQL_HOST,
        port=MYSQL_PORT,
        user=MYSQL_USER,
        password=MYSQL_PASS,
        database=MYSQL_DB,
        charset='utf8mb4'
    )

    try:
        with conn.cursor() as cursor:
            sql = f"""
                INSERT INTO {MYSQL_TABLE}
                (chat_id, chat_name, sender_id, sender_name, message_id, parent_id, root_id, 
                 quote_message_id, msg_type, content, forward_detail, media_url, msg_time, msg_timestamp)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    sender_name = VALUES(sender_name),
                    parent_id = VALUES(parent_id),
                    root_id = VALUES(root_id),
                    quote_message_id = VALUES(quote_message_id),
                    msg_type = VALUES(msg_type),
                    content = VALUES(content),
                    forward_detail = VALUES(forward_detail),
                    media_url = VALUES(media_url)
            """
            
            for record in records:
                cursor.execute(sql, (
                    record['chat_id'],
                    record['chat_name'],
                    record['sender_id'],
                    record['sender_name'],
                    record['message_id'],
                    record['parent_id'],
                    record['root_id'],
                    record['quote_message_id'],
                    record['msg_type'],
                    record['content'],
                    record.get('forward_detail'),
                    record['media_url'],
                    record['msg_time'],
                    record['msg_timestamp']
                ))
            
            conn.commit()
            log.info(f"MySQL写入成功: {len(records)} 条记录")
    except Exception as e:
        log.error(f"MySQL写入失败: {e}")
        conn.rollback()
    finally:
        conn.close()


def main():
    os.makedirs(WORK_DIR, exist_ok=True)

    # 读取上次同步时间
    if os.path.exists(LAST_SYNC_FILE):
        with open(LAST_SYNC_FILE) as f:
            last_sync_iso = f.read().strip()
    else:
        last_sync_iso = (datetime.now(TZ) - timedelta(hours=1)).isoformat()

    current_iso = datetime.now(TZ).isoformat()
    log.info(f"开始同步: {last_sync_iso} -> {current_iso}")

    # 拉取消息
    messages = fetch_messages(last_sync_iso, current_iso)
    if not messages:
        log.info("没有新消息，同步结束")
        with open(LAST_SYNC_FILE, 'w') as f:
            f.write(current_iso)
        return

    log.info(f"发现 {len(messages)} 条新消息")

    # 初始化COS
    cos_uploader = CosUploader()
    date_str = datetime.now(TZ).strftime('%Y-%m-%d')

    # 处理消息
    records = []
    for msg in messages:
        msg_type = msg.get('msg_type', '')
        if msg_type == 'post':
            # post 富文本消息：拆分为文本+图片多条记录
            post_records = process_post_message(msg, cos_uploader, date_str)
            records.extend(post_records)
        elif msg_type == 'merge_forward':
            # 多条转发：解析子消息、下载媒体→COS、生成forward_detail
            fwd_record = process_merge_forward_message(msg, cos_uploader, date_str)
            if fwd_record:
                records.append(fwd_record)
        else:
            record = process_message(msg, cos_uploader, date_str)
            if record:
                records.append(record)

    # 写入数据库
    if records:
        insert_to_mysql(records)
        log.info(f"同步完成: {len(records)} 条记录")
    else:
        log.info("无有效消息需要写入")

    # 更新同步时间
    with open(LAST_SYNC_FILE, 'w') as f:
        f.write(current_iso)


if __name__ == '__main__':
    main()
