import sys
import re
import json
import time
import random
import struct
import requests
from base64 import b64encode, b64decode
from urllib.parse import urlencode
from urllib3 import disable_warnings
from base.spider import Spider

disable_warnings()


class Spider(Spider):
    # ==================== 加密常量 ====================
    DELTA = 0x9e3779b9
    ROUNDS = 16
    LOG_ROUNDS = 4
    SALT_LEN = 2
    ZERO_LEN = 7
    TEA_CKEY = bytes.fromhex('59b2f7cf725ef43c34fdd7c123411ed3')
    GUARD_TEA_KEY = bytes.fromhex('110DBEC10C23E7D2E56A1CAD6914EF1B')

    XOR_KEY = [0x84, 0x2E, 0xED, 0x08, 0xF0, 0x66, 0xE6, 0xEA,
               0x48, 0xB4, 0xCA, 0xA9, 0x91, 0xED, 0x6F, 0xF3]
    GUARD_XOR_KEY = [0xB3, 0xC9, 0x53, 0xA0, 0x69, 0x13, 0xAD, 0x4D]

    STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
    CUSTOM_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-='

    # ==================== 频道图标 ====================
    # 使用 wanglindl/TVlogo 小尺寸台标源（jsdelivr CDN，已验证可用）
    # 图标尺寸较小，适合TVBox/直播列表显示
    LOGOS = {
        'cctv1': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV1.png',
        'cctv2': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV2.png',
        'cctv3': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV3.png',
        'cctv4': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV4.png',
        'cctv5': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV5.png',
        'cctv5p': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV5plus.png',
        'cctv6': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV6.png',
        'cctv7': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV7.png',
        'cctv8': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV8.png',
        'cctv9': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV9.png',
        'cctv10': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV10.png',
        'cctv11': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV11.png',
        'cctv12': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV12.png',
        'cctv13': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV13.png',
        'cctv14': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV14.png',
        'cctv15': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV15.png',
        'cctv16': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV16.png',
        'cctv164k': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV16.png',
        'cctv17': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV17.png',
        'cctv4k': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV4K.png',
        'cctv8k': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTV8K.png',
        'cgtn': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTN.png',
        'cgtnfy': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTNfy.png',
        'cgtney': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTNey.png',
        'cgtnalby': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTNalby.png',
        'cgtnxby': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTNxbyy.png',
        'cgtnwyjl': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CGTNjilu.png',
        'cctvfyjc': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVfyjc.png',
        'cctvdyjc': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVdyjc.png',
        'cctvhjjc': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVhjjc.png',
        'cctvsjdl': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVsjdl.png',
        'cctvfyyy': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVfyyy.png',
        'cctvbqkj': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVbqkj.png',
        'cctvfyzq': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVfyzq.png',
        'cctvgeqwq': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVgefwq.png',
        'cctvnxss': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVnxss.png',
        'cctvyswhjp': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVyswhjp.png',
        'cctvystq': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVystq.png',
        'cctvdszn': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVdszn.png',
        'cctvwsjk': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CCTVwsjk.png',
        'bjws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Beijing.png',
        'jsws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Jiangsu.png',
        'dfws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Dongfang.png',
        'zjws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Zhejiang.png',
        'hnws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Hunan.png',
        'hbws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Hubei.png',
        'gdws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Guangdong.png',
        'gxws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Guangxi.png',
        'hljws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Heilongjiang.png',
        'hnws2': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Hainan.png',
        'cqws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Chongqing.png',
        'szws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Shenzhen.png',
        'scws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Sichuan.png',
        'henanws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Henan.png',
        'fjdnhz': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Dongnan.png',
        'gzhws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Guizhou.png',
        'jxws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Jiangxi.png',
        'lnws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Liaoning.png',
        'ahws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Anhui.png',
        'hbws2': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Hebei.png',
        'sdws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Shandong.png',
        'tjws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Tianjin.png',
        'jlws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Jilin.png',
        'shanxiws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Shanxi.png',
        'nxws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Ningxia.png',
        'nmgws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Neimeng.png',
        'ynws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Yunnan.png',
        'shanxiws2': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Shanxi_.png',
        'qhws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Qinghai.png',
        'xzws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Xizang.png',
        'xjws': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/Xinjiang.png',
        'cetv1': 'https://cdn.jsdelivr.net/gh/wanglindl/TVlogo@main/img/CETV1.png',
        'gxpd': '',
    }

    # ==================== 频道数据 ====================
    # {key: [cnlid, livepid, defn, display_name]}
    CHANNELS = {
        'cctv1': ['2024078201', '600001859', 'fhd', 'CCTV-1'],
        'cctv2': ['2024075401', '600001800', 'fhd', 'CCTV-2'],
        'cctv3': ['2024068501', '600001801', 'fhd', 'CCTV-3'],
        'cctv4': ['2029797101', '600001814', 'fhd', 'CCTV-4'],
        'cctv5': ['2024078401', '600001818', 'fhd', 'CCTV-5'],
        'cctv5p': ['2024078001', '600001817', 'fhd', 'CCTV-5+'],
        'cctv6': ['2013693901', '600108442', 'fhd', 'CCTV-6'],
        'cctv7': ['2024072001', '600004092', 'fhd', 'CCTV-7'],
        'cctv8': ['2029793001', '600001803', 'fhd', 'CCTV-8'],
        'cctv9': ['2024078601', '600004078', 'fhd', 'CCTV-9'],
        'cctv10': ['2024078701', '600001805', 'fhd', 'CCTV-10'],
        'cctv11': ['2027248701', '600001806', 'fhd', 'CCTV-11'],
        'cctv12': ['2027248801', '600001807', 'fhd', 'CCTV-12'],
        'cctv13': ['2029797201', '600001811', 'fhd', 'CCTV-13'],
        'cctv14': ['2027248901', '600001809', 'fhd', 'CCTV-14'],
        'cctv15': ['2027249001', '600001815', 'fhd', 'CCTV-15'],
        'cctv16': ['2027249101', '600098637', 'fhd', 'CCTV-16'],
        'cctv164k': ['2027249301', '600099502', 'fhd', 'CCTV-16(4K)'],
        'cctv17': ['2027249401', '600001810', 'fhd', 'CCTV-17'],
        'cctv4k': ['2029810301', '600002264', 'fhd', 'CCTV-4K'],
        'cctv8k': ['2026774101', '600156816', 'fhd', 'CCTV-8K'],
        'cgtn': ['2024181701', '600014550', 'fhd', 'CGTN'],
        'cgtnfy': ['2024181801', '600084704', 'fhd', 'CGTN法语'],
        'cgtney': ['2024181901', '600084758', 'fhd', 'CGTN俄语'],
        'cgtnalby': ['2024182001', '600084782', 'fhd', 'CGTN阿拉伯语'],
        'cgtnxby': ['2024182101', '600084744', 'fhd', 'CGTN西班牙语'],
        'cgtnwyjl': ['2024182301', '600084781', 'fhd', 'CGTN外语纪录'],
        'cctvfyjc': ['2025637103', '600099658', 'shd', '风云剧场'],
        'cctvdyjc': ['2026874203', '600099655', 'shd', '第一剧场'],
        'cctvhjjc': ['2026874303', '600099620', 'shd', '怀旧剧场'],
        'cctvsjdl': ['2026874403', '600099637', 'shd', '世界地理'],
        'cctvfyyy': ['2026874503', '600099660', 'shd', '风云音乐'],
        'cctvbqkj': ['2026874603', '600099649', 'shd', '兵器科技'],
        'cctvfyzq': ['2026966203', '600099636', 'shd', '风云足球'],
        'cctvgeqwq': ['2026874703', '600099659', 'shd', '高尔夫·网球'],
        'cctvnxss': ['2026874803', '600099650', 'shd', '女性时尚'],
        'cctvyswhjp': ['2026874903', '600099653', 'shd', '央视文化精品'],
        'cctvystq': ['2026875003', '600099652', 'shd', '央视台球'],
        'cctvdszn': ['2026875103', '600099656', 'shd', '电视指南'],
        'cctvwsjk': ['2025637003', '600099651', 'shd', '卫生健康'],
        'bjws': ['2024052703', '600002309', 'fhd', '北京卫视'],
        'jsws': ['2024171103', '600002521', 'fhd', '江苏卫视'],
        'dfws': ['2024054503', '600002483', 'fhd', '东方卫视'],
        'zjws': ['2024054703', '600002520', 'fhd', '浙江卫视'],
        'hnws': ['2024054803', '600002475', 'fhd', '湖南卫视'],
        'hbws': ['2024171203', '600002508', 'fhd', '湖北卫视'],
        'gdws': ['2024060903', '600002485', 'fhd', '广东卫视'],
        'gxws': ['2024060703', '600002509', 'fhd', '广西卫视'],
        'hljws': ['2029797003', '600002498', 'fhd', '黑龙江卫视'],
        'hnws2': ['2024055603', '600002506', 'fhd', '海南卫视'],
        'cqws': ['2024061103', '600002531', 'fhd', '重庆卫视'],
        'szws': ['2024061303', '600002481', 'fhd', '深圳卫视'],
        'scws': ['2024061403', '600002516', 'fhd', '四川卫视'],
        'henanws': ['2029797303', '600002525', 'fhd', '河南卫视'],
        'fjdnhz': ['2024061503', '600002484', 'fhd', '福建东南卫视'],
        'gzhws': ['2024061603', '600002490', 'fhd', '贵州卫视'],
        'jxws': ['2024061703', '600002503', 'fhd', '江西卫视'],
        'lnws': ['2024171303', '600002505', 'fhd', '辽宁卫视'],
        'ahws': ['2024171403', '600002532', 'fhd', '安徽卫视'],
        'hbws2': ['2024171503', '600002493', 'fhd', '河北卫视'],
        'sdws': ['2029787903', '600002513', 'fhd', '山东卫视'],
        'tjws': ['2019927003', '600152137', 'fhd', '天津卫视'],
        'jlws': ['2025561503', '600190405', 'fhd', '吉林卫视'],
        'shanxiws': ['2029795103', '600190400', 'fhd', '陕西卫视'],
        'nxws': ['2025608503', '600190737', 'fhd', '宁夏卫视'],
        'nmgws': ['2025561203', '600190401', 'fhd', '内蒙古卫视'],
        'ynws': ['2025561303', '600190402', 'fhd', '云南卫视'],
        'shanxiws2': ['2025560803', '600190407', 'fhd', '山西卫视'],
        'qhws': ['2025559103', '600190406', 'fhd', '青海卫视'],
        'xzws': ['2025558003', '600190403', 'fhd', '西藏卫视'],
        'xjws': ['2019927403', '600152138', 'fhd', '新疆卫视'],
        'cetv1': ['2022823801', '600171827', 'fhd', '中国教育电视台'],
        'gxpd': ['2029360403', '600213139', 'fhd', '国学频道'],
    }

    # ==================== 分类定义 ====================
    CATS = [
        {
            'name': '央视',
            'ids': [
                'cctv1', 'cctv2', 'cctv3', 'cctv4', 'cctv5', 'cctv5p',
                'cctv6', 'cctv7', 'cctv8', 'cctv9', 'cctv10', 'cctv11',
                'cctv12', 'cctv13', 'cctv14', 'cctv15', 'cctv16', 'cctv164k',
                'cctv17', 'cctv4k', 'cctv8k',
            ]
        },
        {
            'name': 'CGTN',
            'ids': ['cgtn', 'cgtnfy', 'cgtney', 'cgtnalby', 'cgtnxby', 'cgtnwyjl']
        },
        {
            'name': '央视付费',
            'ids': [
                'cctvfyjc', 'cctvdyjc', 'cctvhjjc', 'cctvsjdl', 'cctvfyyy',
                'cctvbqkj', 'cctvfyzq', 'cctvgeqwq', 'cctvnxss', 'cctvyswhjp',
                'cctvystq', 'cctvdszn', 'cctvwsjk',
            ]
        },
        {
            'name': '卫视',
            'ids': [
                'bjws', 'jsws', 'dfws', 'zjws', 'hnws', 'hbws', 'gdws',
                'gxws', 'hljws', 'hnws2', 'cqws', 'szws', 'scws', 'henanws',
                'fjdnhz', 'gzhws', 'jxws', 'lnws', 'ahws', 'hbws2', 'sdws',
                'tjws', 'jlws', 'shanxiws', 'nxws', 'nmgws', 'ynws',
                'shanxiws2', 'qhws', 'xzws', 'xjws',
            ]
        },
        {
            'name': '其他',
            'ids': ['cetv1', 'gxpd']
        },
    ]

    # ==================== 初始化 ====================

    def init(self, extend=""):
        self.guid = self._gen_guid()
        self.session = requests.Session()
        self.session.verify = False
        self.session.headers.update({
            'User-Agent': 'qqlive',
            'Connection': 'Keep-Alive',
            'Accept': 'application/json',
        })

    def getName(self):
        return "央视频"

    # ==================== 蜘蛛壳接口 ====================

    def homeContent(self, filter):
        classes = [
            {'type_id': str(i), 'type_name': c['name']}
            for i, c in enumerate(self.CATS)
        ]
        return {'class': classes}

    def categoryContent(self, tid, pg, filter, extend):
        cat = self.CATS[int(tid)]
        vod_list = []
        for cid in cat['ids']:
            ch = self.CHANNELS.get(cid)
            if ch:
                vod_list.append({
                    'vod_id': cid,
                    'vod_name': ch[3],
                    'vod_pic': self.LOGOS.get(cid, ''),
                    'vod_remarks': ch[2].upper(),
                })
        return {
            'page': 1,
            'pagecount': 1,
            'limit': len(vod_list),
            'total': len(vod_list),
            'list': vod_list,
        }

    def detailContent(self, ids):
        cid = ids[0]
        ch = self.CHANNELS.get(cid)
        if not ch:
            return {'list': []}
        vod = {
            'vod_id': cid,
            'vod_name': ch[3],
            'vod_pic': self.LOGOS.get(cid, ''),
            'vod_play_from': '央视频',
            'vod_play_url': f'直播${cid}',
            'vod_content': f'{ch[3]} 高清直播',
        }
        return {'list': [vod]}

    def playerContent(self, flag, id, vipFlags):
        ch = self.CHANNELS.get(id)
        if not ch:
            return {'parse': 0, 'url': '', 'header': ''}
        play_url = self._get_play_url(ch[0], ch[1], ch[2])
        if play_url:
            return {'parse': 0, 'url': play_url, 'header': ''}
        return {'parse': 0, 'url': '', 'header': ''}

    # ==================== 播放地址获取 ====================

    def _get_play_url(self, cnlid, livepid, defn):
        try:
            self.guid = self._gen_guid()
            ck = self._gen_ckey(cnlid)
            flowid = self._gen_flowid()

            params = {
                "atime": "120",
                "livepid": livepid,
                "cnlid": cnlid,
                "appVer": "V8.22.1035.3031",
                "app_version": "300090",
                "caplv": "1",
                "cmd": "2",
                "defn": defn,
                "device": "iPhone",
                "encryptVer": "4.2",
                "getpreviewinfo": "0",
                "hevclv": "33",
                "lang": "zh-Hans_JP",
                "livequeue": "0",
                "logintype": "1",
                "nettype": "1",
                "newnettype": "1",
                "newplatform": "4330403",
                "platform": "4330403",
                "sdtfrom": "v3021",
                "spacode": "23",
                "spaudio": "1",
                "spdemuxer": "6",
                "spdrm": "2",
                "spdynamicrange": "7",
                "spflv": "1",
                "spflvaudio": "1",
                "sphdrfps": "60",
                "sphttps": "0",
                "spvcode": "MSgzMDoyMTYwLDYwOjIxNjB8MzA6MjE2MCw2MDoyMTYwKTsyKDMwOjIxNjAsNjA6MjE2MHwzMDoyMTYwLDYwOjIxNjAp",
                "spvideo": "4",
                "stream": "1",
                "system": "1",
                "sysver": "ios18.2.1",
                "uhd_flag": "4",
                "cKey": ck['ckey'],
                "guid": self.guid,
                "fntick": ck['params']['Timestamp'],
                "flowid": flowid,
                "playbacktime": "0",
            }

            resp = self.session.get(
                "https://bkliveinfo.ysp.cctv.cn",
                params=params,
                timeout=15,
            )
            data = resp.json()
            if data.get('iretcode') == 0 and data.get('playurl'):
                return data['playurl']
        except Exception as e:
            print(f"[央视频] 获取播放地址失败: {e}")
        return None

    # ==================== GUID / FlowID ====================

    def _gen_guid(self):
        return '{:08x}{:04x}{:04x}{:04x}{:012x}'.format(
            random.randint(0, 0xffffffff),
            random.randint(0, 0xffff),
            random.randint(0, 0xffff),
            random.randint(0, 0xffff),
            random.randint(0, 0xffffffffffff),
        )

    def _gen_flowid(self):
        p = [
            random.randint(0, 0xffff), random.randint(0, 0xffff),
            random.randint(0, 0xffff),
            random.randint(0, 0x0fff) | 0x4000,
            random.randint(0, 0x3fff) | 0x8000,
            random.randint(0, 0xffff), random.randint(0, 0xffff),
            random.randint(0, 0xffff),
        ]
        return '{:04X}{:04X}-{:04X}-{:04X}-{:04X}-{:04X}{:04X}{:04X}_4330403'.format(*p)

    # ==================== 签名 ====================

    def _calc_sig(self, buf):
        s = 0
        for b in buf:
            s = (0x83 * s + (b & 0xFF)) & 0x7FFFFFFF
        return s

    # ==================== 自定义 Base64 ====================

    def _b64enc(self, data):
        enc = b64encode(data).decode()
        return enc.translate(str.maketrans(self.STANDARD_ALPHABET, self.CUSTOM_ALPHABET)).rstrip('=')

    def _b64dec(self, text):
        text = text.rstrip('=')
        if len(text) % 4:
            text += '=' * (4 - len(text) % 4)
        return b64decode(text.translate(str.maketrans(self.CUSTOM_ALPHABET, self.STANDARD_ALPHABET)))

    # ==================== XOR ====================

    def _xor(self, arr):
        return [arr[i] ^ self.XOR_KEY[i & 0xF] for i in range(len(arr))]

    # ==================== TEA ====================

    def _tea_enc(self, data, key):
        if len(data) < 8:
            data = data.ljust(8, b'\0')
        y, z = struct.unpack('>II', data[:8])
        k = struct.unpack('>4I', key[:16])
        s = 0
        for _ in range(self.ROUNDS):
            s = (s + self.DELTA) & 0xFFFFFFFF
            y = (y + (((z << 4) + k[0]) ^ (z + s) ^ ((z >> 5) + k[1]))) & 0xFFFFFFFF
            z = (z + (((y << 4) + k[2]) ^ (y + s) ^ ((y >> 5) + k[3]))) & 0xFFFFFFFF
        return struct.pack('>II', y, z)

    def _tea_dec(self, data, key):
        y, z = struct.unpack('>II', data[:8])
        k = struct.unpack('>4I', key[:16])
        s = (self.DELTA << self.LOG_ROUNDS) & 0xFFFFFFFF
        for _ in range(self.ROUNDS):
            z = (z - (((y << 4) + k[2]) ^ (y + s) ^ ((y >> 5) + k[3]))) & 0xFFFFFFFF
            y = (y - (((z << 4) + k[0]) ^ (z + s) ^ ((z >> 5) + k[1]))) & 0xFFFFFFFF
            s = (s - self.DELTA) & 0xFFFFFFFF
        return struct.pack('>II', y, z)

    # ==================== CBC 加密 ====================

    def _cbc_enc(self, p_in, n_len, p_key):
        pad_salt_zero = n_len + 1 + self.SALT_LEN + self.ZERO_LEN
        n_pad = pad_salt_zero % 8
        if n_pad:
            n_pad = 8 - n_pad

        out = b''
        src = [0] * 8
        src[0] = (random.randint(0, 255) & 0xF8) | n_pad
        si = 1

        while n_pad:
            src[si] = random.randint(0, 255)
            si += 1
            n_pad -= 1

        iv_p = [0] * 8
        iv_c = [0] * 8

        # salt
        i = 0
        while i < self.SALT_LEN:
            if si < 8:
                src[si] = random.randint(0, 255)
                si += 1
                i += 1
            if si == 8:
                for j in range(8):
                    src[j] ^= iv_c[j]
                tb = list(self._tea_enc(bytes(src), p_key))
                for j in range(8):
                    tb[j] ^= iv_p[j]
                iv_p = list(src)
                iv_c = list(tb)
                out += bytes(tb)
                si = 0

        # body
        pi = 0
        while n_len:
            if si < 8:
                src[si] = p_in[pi]
                pi += 1
                si += 1
                n_len -= 1
            if si == 8:
                for j in range(8):
                    src[j] ^= iv_c[j]
                tb = list(self._tea_enc(bytes(src), p_key))
                for j in range(8):
                    tb[j] ^= iv_p[j]
                iv_p = list(src)
                iv_c = list(tb)
                out += bytes(tb)
                si = 0

        # zero
        i = 0
        while i < self.ZERO_LEN:
            if si < 8:
                src[si] = 0
                si += 1
                i += 1
            if si == 8:
                for j in range(8):
                    src[j] ^= iv_c[j]
                tb = list(self._tea_enc(bytes(src), p_key))
                for j in range(8):
                    tb[j] ^= iv_p[j]
                iv_p = list(src)
                iv_c = list(tb)
                out += bytes(tb)
                si = 0

        # last
        if si > 0:
            for j in range(si, 8):
                src[j] = 0
            for j in range(8):
                src[j] ^= iv_c[j]
            tb = list(self._tea_enc(bytes(src), p_key))
            for j in range(8):
                tb[j] ^= iv_p[j]
            out += bytes(tb)

        return out

    # ==================== CBC 解密 ====================

    def _cbc_dec(self, p_in, n_len, p_key):
        if n_len % 8 != 0 or n_len < 16:
            return None
        dest = list(self._tea_dec(p_in[:8], p_key))
        n_pad = dest[0] & 0x07
        out_len = n_len - 1 - n_pad - self.SALT_LEN - self.ZERO_LEN
        if out_len < 0:
            return None

        iv_pre = [0] * 8
        iv_cur = list(p_in[:8])
        off = 8
        di = 1 + n_pad

        # skip salt
        sc = 1
        while sc <= self.SALT_LEN:
            if di < 8:
                di += 1
                sc += 1
            elif di == 8:
                iv_pre = list(iv_cur)
                if off + 8 > n_len:
                    return None
                iv_cur = list(p_in[off:off + 8])
                for j in range(8):
                    dest[j] ^= iv_cur[j]
                dest = list(self._tea_dec(bytes(dest), p_key))
                off += 8
                di = 0

        # copy plain
        plain = []
        rem = out_len
        while rem > 0:
            if di < 8:
                plain.append(dest[di] ^ iv_pre[di])
                di += 1
                rem -= 1
            elif di == 8:
                iv_pre = list(iv_cur)
                if off + 8 > n_len:
                    return None
                iv_cur = list(p_in[off:off + 8])
                for j in range(8):
                    dest[j] ^= iv_cur[j]
                dest = list(self._tea_dec(bytes(dest), p_key))
                off += 8
                di = 0
        return bytes(plain)

    # ==================== Guard Time ====================

    def _last5(self, v):
        v = str(v)
        return v[-5:] if len(v) >= 5 else ''

    def _gen_guard_time(self, ts, guid):
        body = struct.pack('>I', ts)
        for part in [self._last5(guid), self._last5('null'), self._last5('null'), '-1']:
            pb = part.encode()
            body += struct.pack('>H', len(pb)) + pb

        plain = struct.pack('>H', len(body)) + body
        chk = self._calc_sig(list(plain))

        enc = self._cbc_enc(plain, len(plain), self.GUARD_TEA_KEY) + struct.pack('>I', chk)
        el = list(enc)
        for i in range(len(el)):
            el[i] ^= self.GUARD_XOR_KEY[i & 7]
        return bytes(el).hex().upper()

    # ==================== CKey ====================

    def _encrypt_ckey(self, data):
        chk = self._calc_sig(list(data))
        enc = self._cbc_enc(data, len(data), self.TEA_CKEY) + struct.pack('>I', chk)
        return '--01' + self._b64enc(bytes(self._xor(list(enc))))

    def _build_pkt(self, params):
        d = b''
        d += bytes.fromhex('0000004200000004000004d2')  # 12-byte header
        d += struct.pack('>I', params['Platform'])
        d += struct.pack('>I', 0)  # sig placeholder
        d += struct.pack('>I', params['Timestamp'])

        for k in ['Sdtfrom', 'randFlag', 'appVer', 'vid', 'guid']:
            v = params[k].encode()
            d += struct.pack('>H', len(v)) + v

        d += struct.pack('>I', 1)  # part1
        d += struct.pack('>I', 1)  # isDlna

        for v in [b'2622783A', b'nil']:
            d += struct.pack('>H', len(v)) + v

        uuid4 = params['uuid4'].encode()
        d += struct.pack('>H', len(uuid4)) + uuid4

        d += struct.pack('>H', 3) + b'nil'  # bundleID1

        for v in [b'v0.1.000', b'com.cctv.yangshipin.app.iphone',
                  b'4330403', b'ex_json_bus', b'ex_json_vs']:
            d += struct.pack('>H', len(v)) + v

        cgt = params['ck_guard_time'].encode()
        d += struct.pack('>H', len(cgt)) + cgt

        buf = struct.pack('>H', len(d)) + d
        sig = self._calc_sig(list(buf))
        return buf[:18] + struct.pack('>I', sig) + buf[22:]

    def _gen_ckey(self, cnlid):
        ts = int(time.time())
        cgt = self._gen_guard_time(ts, self.guid)
        params = {
            'Platform': 4330403,
            'Timestamp': ts,
            'Sdtfrom': 'dcgh',
            'vid': cnlid,
            'guid': self.guid,
            'appVer': 'V8.22.1035.3031',
            'randFlag': '_zj1A5Gh6QYcxWjIUGos2w==',
            'uuid4': '57eab0c4-2c58-44c6-8ae9-dd2757525dc5',
            'ck_guard_time': cgt,
        }
        pkt = self._build_pkt(params)
        return {'ckey': self._encrypt_ckey(pkt), 'params': params}