[{"bookSourceComment":"七猫小说，该书源我是让AI以MD3版本进行修改的，其余版本可能会出现不可用的问题，具体请自行摸索。","bookSourceGroup":"🌙 七猫聚合","bookSourceName":"🌙 七猫聚合 · 小说·听书·短剧·漫剧 MD3版用，其余版本请自行摸索","bookSourceType":0,"bookSourceUrl":"https:\/\/api-bc.wtzw.com\/#qimao-aggregate-md3","customButton":false,"customOrder":0,"enabled":true,"enabledCookieJar":false,"enabledExplore":true,"eventListener":false,"exploreUrl":"@js:QMA_DISC.explore.call(this,typeof infoMap===\"undefined\"?null:infoMap)","jsLib":"var QM_KEY = 'd3dGiJc651gSQ8w1';\nvar QM_AES = '242ccb8230d709e1';\nvar QM_BC = 'https:\/\/api-bc.wtzw.com';\nvar QM_KS = 'https:\/\/api-ks.wtzw.com';\n\nfunction qmMd5(s) {\n    var b = Packages.java.security.MessageDigest.getInstance('MD5')\n        .digest(new Packages.java.lang.String(s).getBytes('UTF-8'));\n    var h = '';\n    for (var i = 0; i < b.length; i++) {\n        var x = (b[i] & 0xff).toString(16);\n        h += (x.length < 2 ? '0' : '') + x;\n    }\n    return h;\n}\n\nfunction qmSign(o) {\n    return qmMd5(Object.keys(o).sort().map(function (k) { return k + '=' + o[k]; }).join('') + QM_KEY);\n}\n\nvar QM_HEADERS = (function () {\n    var h = {'app-version': '80400', 'platform': 'android', 'reg': '0', 'AUTHORIZATION': '',\n             'application-id': 'com.****.reader', 'net-env': '1', 'channel': 'unknown', 'qm-params': ''};\n    h.sign = qmSign(h);\n    return JSON.stringify({headers: h});\n})();\n\nfunction qmUrl(base, path, params) {\n    params.sign = qmSign(params);\n    var q = Object.keys(params).map(function (k) {\n        return k + '=' + encodeURIComponent(params[k]);\n    }).join('&');\n    return base + path + '?' + q + ',' + QM_HEADERS;\n}\n\nfunction qmSearch(key, page) {\n    return qmUrl(QM_BC, '\/search\/v1\/words', {gender: '3', imei_ip: '2937357107', page: page, wd: key});\n}\nfunction qmDetail(id) {\n    return qmUrl(QM_BC, '\/api\/v4\/book\/detail', {id: id, imei_ip: '2937357107', teeny_mode: 0});\n}\nfunction qmToc(id) {\n    return qmUrl(QM_KS, '\/api\/v1\/chapter\/chapter-list', {id: id});\n}\nfunction qmTag(gender, tid, page, over, words, sort) {\n    var p = {gender: gender, need_filters: 1, page: page, tag_id: tid,\n        over: over == null ? -99 : over, words: words == null ? -99 : words, sort: sort == null ? 0 : sort};\n    if (gender == 1 || gender == 2) p.book_preference = gender;\n    return qmUrl(QM_BC, '\/api\/v4\/tag\/index', p);\n}\n\nfunction qmContent(bid, cid, agent) {\n    var p = {id: bid, chapterId: cid};\n    if (agent) p.reader_agent = '1';\n    return qmUrl(QM_KS, '\/api\/v1\/chapter\/content', p);\n}\n\nfunction qmChapterUrl(cid, md5, agent) {\n    var s = cid + '\/' + md5 + '\/' + (agent == '1' ? '1' : '0');\n    var b64 = Packages.java.util.Base64.getEncoder()\n        .encodeToString(new Packages.java.lang.String(s).getBytes('UTF-8'));\n    return 'data:;base64,' + b64 + ',{\"type\":\"qm\"}';\n}\n\nfunction qmUnzipEpub(b) {\n    function u16(p) { return (b[p] & 0xff) | (b[p + 1] & 0xff) << 8; }\n    function u32(p) { return (u16(p) | u16(p + 2) << 16) >>> 0; }\n    var eocd = b.length - 22;\n    while (eocd >= 0 && u32(eocd) != 0x06054b50) eocd--;\n    if (eocd < 0) return '';\n    var p = u32(eocd + 16), count = u16(eocd + 10), html = '';\n    for (var i = 0; i < count; i++) {\n        var method = u16(p + 10), csize = u32(p + 20), usize = u32(p + 24);\n        var nlen = u16(p + 28), total = 46 + nlen + u16(p + 30) + u16(p + 32);\n        var name = String(new Packages.java.lang.String(b, p + 46, nlen, 'UTF-8'));\n        var lh = u32(p + 42);\n        p += total;\n        if (!\/\\.x?html?$\/i.test(name)) continue;\n        var start = lh + 30 + u16(lh + 26) + u16(lh + 28), text;\n        if (method == 0) {\n            text = new Packages.java.lang.String(b, start, csize, 'UTF-8');\n        } else {\n            var inf = new Packages.java.util.zip.Inflater(true);\n            inf.setInput(b, start, csize);\n            var out = Packages.java.util.Arrays.copyOf(b, usize);\n            inf.inflate(out);\n            inf.end();\n            text = new Packages.java.lang.String(out, 'UTF-8');\n        }\n        var m = String(text).match(\/<body[^>]*>([\\s\\S]*)<\\\/body>\/i);\n        if (m) html += m[1];\n    }\n    return html.replace(\/<h\\d[^>]*>[\\s\\S]*?<\\\/h\\d>\/gi, '')\n               .replace(\/&amp;\/g, '&').trim();\n}\n\nfunction qmDecode(content) {\n    var A = Packages.java.util.Arrays;\n    var raw = Packages.java.util.Base64.getDecoder().decode(String(content));\n    var key = new Packages.javax.crypto.spec.SecretKeySpec(new Packages.java.lang.String(QM_AES).getBytes('UTF-8'), 'AES');\n    var iv = new Packages.javax.crypto.spec.IvParameterSpec(A.copyOfRange(raw, 0, 16));\n    var c = Packages.javax.crypto.Cipher.getInstance('AES\/CBC\/PKCS5Padding');\n    c.init(2, key, iv);\n    var b = c.doFinal(A.copyOfRange(raw, 16, raw.length));\n    if (b.length > 3 && b[0] == 0x50 && b[1] == 0x4B) return qmUnzipEpub(b);\n    return String(new Packages.java.lang.String(b, 'UTF-8'));\n}\n\nvar QM_TAGS = {\n    '男频': [\n        ['热门', '爽文:570,都市:758,年代:672,玄幻:717,兵王:28,玄幻脑洞:884,扮猪吃虎:794,热血:1,多角色:1409,杀伐果断:202,穿越:373,逆袭:400,七猫金选:1385'],\n        ['情节', '都市脑洞:883,玄幻脑洞:884,穿越:373,逆袭:400,剑道:72,打脸虐渣:739,无敌流:305,重生:779,系统:782,多女主:945,金手指:756,脑洞:526,修仙:659,复仇:790,强者回归:402,致富:492,随身空间:345,争霸:312,奇闻异事:769,异能:644,风水:437,草根崛起:1223,穿书:338,影视:539,一统天下:410,单女主:861,职场:741,影视原著:1333,契约婚姻:355,古武:606,后悔流:1202,开局流:564,娱乐圈:357,推理:668,武魂:427,游戏:714,鉴宝:47,美食:300,修罗场:1256,进化:528,盗墓:327,技术流:663,探险:697,无限流:557,历史脑洞:885,签到:565,直播:419,网游:740,长生流:566,丧尸:632,御兽:423,囤物资:894,爱国流:830,基建:612,通古今:1203,吞噬:530,稳健流:837,科技:855,召唤:313,迪化流:832,国术:613,相亲:909,全民领主:1213,无女主:860,体育:601,偷听心声:1258,综艺:911,机甲:690,恋综:817,电竞:715,克苏鲁:834,篮球:732,国运:868,万物沟通:1285,双男主:818,荒野求生:1226,足球:576,综武:940,NBA:984,弹幕文:1274,崩人设:1257,扮演:842,氪金:1225,多重人格:954'],\n        ['背景', '都市:758,末世:556,玄幻:717,古代:604,架空历史:653,仙侠:589,年代:672,历史:669,异界:692,乡村:599,校园:691,高武:840,诸天万界:307,科幻:730,传统玄幻:1268,奇幻:678,灵气复苏:403,未来世界:704,大明:636,武侠:371,影视衍生:638,洪荒:559,星际:686,大唐:322,四合院:854,神话:726,西幻:744,三国:321,动漫衍生:598,民国:660,西游:324,大秦:858,游戏衍生:673,封神:658,火影:330,海贼王:331,宋朝:943,赛博朋克:957,崩坏星穹铁道:969'],\n        ['角色', '杀伐果断:202,智商在线:806,扮猪吃虎:794,全能:196,仙帝:31,天才:783,医生:156,废柴:791,兵王:28,女总裁:89,护花高手:30,神医:1269,战神:527,王爷:125,赘婿:36,世子:580,皇帝:62,将军:787,反派:792,明星:684,皇子:713,太子:128,学霸:781,道士:74,神豪:37,咸鱼:812,女帝:646,特种兵:547,奶爸:41,校花:92,高冷:1219,太监:642,无节操:1215,弃少:525,律师:161,书生:852,师徒恋:507,龙:444,暴君:703,僵尸:449,主播:587,魔尊:544,侦探:592,特工:188,驸马:946,幕后BOSS:963,佛系:1255,外卖员:958,杀手:784,锦衣卫:757,御姐:1254,奸臣:621,败家子:752,法医:189,真假少爷:1270,女尊:624,嘴炮:1216,丞相:944'],\n        ['风格', '爽文:570,轻松:3,种田:317,权谋:785,搞笑:788,热血:1,快节奏:1222,日常:878,悬疑:27,励志:786,现实题材:12,恐怖:567,商战:675,军事:603,第一人称:977,反套路:1224,烧脑:571,正剧:707']\n    ],\n    '女频': [\n        ['热门', '现代言情:772,甜宠:21,爽文:570,多角色:1409,豪门总裁:682,幻想言情:774,古代言情:773,重生:779,年代:672,穿越:373,逆袭:400,智商在线:806,七猫金选:1385'],\n        ['情节', '快穿:335,穿越:373,逆袭:400,HE:581,1V1:585,系统:782,追妻火葬场:753,先婚后爱:524,重生:779,金手指:756,脑洞:526,修仙:659,复仇:790,致富:492,随身空间:345,奇闻异事:769,打脸虐渣:739,异能:644,玄学:900,风水:437,一见钟情:583,穿书:338,暗恋:689,男二上位:857,破镜重圆:467,影视:539,马甲:558,职场:741,影视原著:1333,契约婚姻:355,闪婚:480,娱乐圈:357,推理:668,青梅竹马:465,婚恋:1246,带球跑:640,美食:300,替嫁:476,相爱相杀:722,修罗场:1256,双向奔赴:844,无限流:557,直播:419,隐婚:481,丧尸:632,御兽:423,囤物资:894,爱国流:830,姐弟恋:631,错嫁:501,父子火葬场:1259,基建:612,无CP:819,通古今:1203,逃荒:864,科技:855,相亲:909,双向救赎:1248,读心术:810,死对头:1239,偷听心声:1258,爆红:1250,综艺:911,机甲:690,恋综:817,天作之合:1244,电竞:715,宠夫:964,古穿今:671,BE:579,万物沟通:1285,双男主:818,倒追:1252,萌宠:1249,逃婚:483,弹幕文:1274,失忆:1251,崩人设:1257,氪金:1225,多重人格:954,双重生:828'],\n        ['背景', '现代言情:772,年代:672,古代:604,兽世:856,古代言情:773,架空历史:653,末世:556,异界:692,幻想言情:774,乡村:599,校园:691,科幻:730,奇幻:678,未来世界:704,武侠:371,影视衍生:638,星际:686,神话:726,西幻:744,动漫衍生:598,民国:660,远古:750,游戏衍生:673,衍生言情:1214,崩坏星穹铁道:969'],\n        ['角色', '豪门总裁:682,智商在线:806,扮猪吃虎:794,霸道:239,全能:196,天才:783,腹黑:789,女强:620,医生:156,萌宝:356,宠妻:1245,人间清醒:1200,双洁:947,团宠:545,可盐可甜:608,强强:533,废柴:791,偏执:248,真假千金:808,嫡女:117,王爷:125,王妃:107,世子:580,大女主:972,白月光:851,皇帝:62,将军:787,糙汉:907,明星:684,忠犬:259,皇子:713,太子:128,反派:792,傲娇:268,学霸:781,二婚:655,医妃:108,独宠:1253,咸鱼:812,女帝:646,女配:191,丫鬟:961,毒舌:265,欢喜冤家:464,首辅:133,高冷:1219,美强惨:1232,戏精:1228,万人迷:1227,痴情:1217,呆萌:609,替身:648,病娇:273,残疾:263,太监:642,丑女:141,大叔:153,锦鲤:813,主母:973,摄政王:698,律师:161,黑化:1247,弃妇:590,奶团:971,书生:852,师徒恋:507,冰山:1242,暴君:703,公主:113,反差萌:1235,庶女:118,炮灰:1229,超A:1218,女扮男装:619,醋王:1221,主播:587,魔尊:544,黑莲花:849,侦探:592,特工:188,心机:1236,校草:701,财迷:1220,皇后:106,古灵精怪:1243,皇叔:721,小狼狗:1234,佛系:1255,作精:948,外卖员:958,杀手:784,锦衣卫:757,御姐:1254,奸臣:621,后妈:845,毒医:1240,吸血鬼:182,仵作:190,绿茶:738,法医:189,厨娘:1241,女尊:624,顶流:1233,黑月光:850,逗比:1238,旺夫:1231,暖男:1237,嘴炮:1216,吃货:1230,恶婆婆:962,双女主:843'],\n        ['风格', '爽文:570,甜宠:21,轻松:3,种田:317,宅斗:342,权谋:785,虐恋:16,搞笑:788,快节奏:1222,日常:878,治愈:17,悬疑:27,励志:786,宫斗:343,现实题材:12,恐怖:567,青春:761,第一人称:977,反套路:1224,烧脑:571,正剧:707,短篇:934,清穿:336']\n    ],\n    '出版': [\n        ['热门', '都市:758,现代言情:772,玄幻:717,古代:604,古代言情:773,历史:669'],\n        ['内容主题', '家庭:687,生活:712,婚姻:628,养生:662,育儿:1321,医学:1320,社会问题:1317'],\n        ['品质认证', '出版:1303,影视原著:1333,名著:610,获奖佳作:742,名家名作:1336,站外权威推荐:1338,豆瓣高分:1337'],\n        ['出版小说', '都市:758,现代言情:772,玄幻:717,古代:604,古代言情:773,历史:669,仙侠:589,悬疑:27,现实题材:12,青春:761,科幻:730,言情:745,武侠:371,群像:1340'],\n        ['读者群体', '女性:643,青少年:540,少儿教育:797,职场人士:1318'],\n        ['个人成长', '心理:649,成功学:696,经管励志:798,情商:639,沟通:702,管理:729,经济:710,自我成长:1329,时间管理:1324,演讲:1328,思维训练:1325,领导力:1323,学习方法:1331,决策:1322,团队建设:1326,职业发展:1330,效率:1327'],\n        ['文学体裁', '传记:591,文学小说:1310,作品集:597,随笔:737,散文:645,评论:724,诗词:747,中篇小说:1309,短篇小说:1308,戏剧:1312,纪实文学:1311,杂文:1313,报告文学:1307'],\n        ['地区文学', '中国文学:1348,法国文学:1349,英国文学:1351,美国文学:1350,德国文学:1352,日本文学:1332,俄罗斯文学:1354,外文原版:1353,意大利文学:1355'],\n        ['时代背景', '社会:723,近代:728,改革开放:1314'],\n        ['社会人文', '人文社科:796,文化历史:1316,文化:685,文学艺术:795,国学:534,哲学:1315,科普:733,艺术:720,世界历史:1319'],\n        ['思想政治', '红色文学:535,思政:694,四史:611']\n    ]\n};\n\n\/\/ MD3 discovery helpers: select controls persist display values in infoMap and\n\/\/ normalized request values in the source variable.\nfunction qmExploreMapGet(map, key) {\n    try {\n        if (map && map.get) return map.get(key);\n    } catch (e) {}\n    try {\n        if (map) return map[key];\n    } catch (e) {}\n    return '';\n}\n\nfunction qmExploreMapPut(map, key, value) {\n    try {\n        if (map && map.put) {\n            map.put(key, value);\n            return;\n        }\n    } catch (e) {}\n    try {\n        if (map && map.set) {\n            map.set(key, value);\n            return;\n        }\n    } catch (e) {}\n    try {\n        if (map) map[key] = value;\n    } catch (e) {}\n}\n\nfunction qmExploreTrim(value) {\n    return String(value == null ? '' : value).replace(\/^\\s+|\\s+$\/g, '');\n}\n\nfunction qmExploreValue(value, fallback) {\n    return qmExploreTrim(value) === '' ? fallback : value;\n}\n\nfunction qmExploreRead(sourceValue) {\n    var raw = '';\n    try {\n        raw = sourceValue && sourceValue.getVariable ? sourceValue.getVariable() : '';\n    } catch (e) {}\n    try {\n        return JSON.parse(String(raw || '{}')) || {};\n    } catch (e) {\n        return {};\n    }\n}\n\nfunction qmExploreWrite(sourceValue, value) {\n    var serialized = JSON.stringify(value || {});\n    try {\n        if (sourceValue && sourceValue.putVariable) {\n            sourceValue.putVariable(serialized);\n            return true;\n        }\n    } catch (e) {}\n    try {\n        if (sourceValue && sourceValue.setVariable) {\n            sourceValue.setVariable(serialized);\n            return true;\n        }\n    } catch (e) {}\n    return false;\n}\n\nfunction qmExploreOptionValue(value, options, fallback) {\n    value = qmExploreTrim(value);\n    for (var i = 0; i < options.length; i++) {\n        if (String(options[i][0]) == value || String(options[i][1]) == value) return String(options[i][1]);\n    }\n    return String(fallback);\n}\n\nfunction qmExploreGroupNames(channel) {\n    var groups = QM_TAGS[channel] || [];\n    return groups.filter(function (group) {\n        return qmExploreTagOptions(channel, group[0]).length > 0;\n    }).map(function (group) { return String(group[0]); });\n}\n\n\/\/ Display names and tag_id values must come from the same validated catalog.\nfunction qmExploreTagOptions(channel, groupName) {\n    var groups = QM_TAGS[channel] || [];\n    for (var i = 0; i < groups.length; i++) {\n        if (String(groups[i][0]) != String(groupName)) continue;\n        var options = [];\n        String(groups[i][1]).split(',').forEach(function (item) {\n            var pair = item.split(':');\n            var name = qmExploreTrim(pair[0]);\n            var id = qmExploreTrim(pair[1]);\n            if (pair.length == 2 && name && \/^[1-9][0-9]*$\/.test(id)) {\n                options.push({name: name, id: id});\n            }\n        });\n        return options;\n    }\n    return [];\n}\n\nfunction qmExploreTagNames(channel, groupName) {\n    return qmExploreTagOptions(channel, groupName).map(function (option) {\n        return option.name;\n    });\n}\n\nfunction qmExploreTagId(channel, groupName, tagName) {\n    var option = qmExploreTagOption(channel, groupName, tagName);\n    return option ? option.id : '';\n}\n\nfunction qmExploreTagOption(channel, groupName, tagName) {\n    var options = qmExploreTagOptions(channel, groupName);\n    for (var i = 0; i < options.length; i++) {\n        if (options[i].name == String(tagName)) return options[i];\n    }\n    return options[0] || null;\n}\n\nfunction qmExploreGender(channel) {\n    return ({'男频': 1, '女频': 2, '出版': 3})[channel] || 1;\n}\n\nfunction qmExploreNormalize(value) {\n    value = value || {};\n    var channel = qmExploreTrim(value.channel || value['频道'] || value.ch) || '男频';\n    if (['男频', '女频', '出版'].indexOf(channel) < 0) channel = '男频';\n\n    var groups = qmExploreGroupNames(channel);\n    var group = qmExploreTrim(value.group || value['分类分组']);\n    if (groups.indexOf(group) < 0) group = groups[0] || '';\n\n    var option = qmExploreTagOption(channel, group, value.tag || value['分类标签']);\n    if (!option) throw new Error('当前频道没有有效的分类标签，请更新书源');\n    var tag = option.name;\n\n    var over = qmExploreOptionValue(qmExploreValue(value.over, value['状态']), [['全部状态', '-99'], ['完结', '1'], ['连载', '0']], '-99');\n    var words = qmExploreOptionValue(qmExploreValue(value.words, value['字数']), [['全部字数', '-99'], ['50万以下', '1'], ['50-100万', '2'], ['100-300万', '3'], ['300万以上', '4']], '-99');\n    var sort = qmExploreOptionValue(qmExploreValue(value.sort, value['排序']), [['按热度', '0'], ['按更新', '1'], ['按评分', '2'], ['按新书', '3']], '0');\n\n    return {channel: channel, group: group, tag: tag, over: over, words: words, sort: sort};\n}\n\nfunction qmExploreStateFromInfo(infoMapValue, sourceValue) {\n    var stored = qmExploreNormalize(qmExploreRead(sourceValue));\n    var value = {\n        channel: qmExploreValue(qmExploreMapGet(infoMapValue, '频道'), stored.channel),\n        group: qmExploreValue(qmExploreMapGet(infoMapValue, '分类分组'), stored.group),\n        tag: qmExploreValue(qmExploreMapGet(infoMapValue, '分类标签'), stored.tag),\n        over: qmExploreValue(qmExploreMapGet(infoMapValue, '状态'), stored.over),\n        words: qmExploreValue(qmExploreMapGet(infoMapValue, '字数'), stored.words),\n        sort: qmExploreValue(qmExploreMapGet(infoMapValue, '排序'), stored.sort)\n    };\n    return qmExploreNormalize(value);\n}\n\nfunction qmSaveExploreState(infoMapValue, sourceValue) {\n    var value = qmExploreStateFromInfo(infoMapValue, sourceValue);\n    qmExploreMapPut(infoMapValue, '频道', value.channel);\n    qmExploreMapPut(infoMapValue, '分类分组', value.group);\n    qmExploreMapPut(infoMapValue, '分类标签', value.tag);\n    qmExploreMapPut(infoMapValue, '状态', value.over == '-99' ? '全部状态' : (value.over == '1' ? '完结' : '连载'));\n    qmExploreMapPut(infoMapValue, '字数', value.words == '-99' ? '全部字数' : ({'1': '50万以下', '2': '50-100万', '3': '100-300万', '4': '300万以上'}[value.words] || '全部字数'));\n    qmExploreMapPut(infoMapValue, '排序', value.sort == '0' ? '按热度' : (value.sort == '1' ? '按更新' : (value.sort == '2' ? '按评分' : '按新书')));\n    try {\n        if (infoMapValue && infoMapValue.saveNow) infoMapValue.saveNow();\n    } catch (e) {}\n    qmExploreWrite(sourceValue, value);\n    return value;\n}\n\nfunction qmBuildExploreUrlFromEncoded(javaValue, encoded, page, sourceValue) {\n    var value;\n    try {\n        value = JSON.parse(decodeURIComponent(String(encoded || '')));\n    } catch (e) {\n        value = {};\n    }\n    value = qmExploreNormalize(value);\n    if (sourceValue) qmExploreWrite(sourceValue, value);\n    var tag = qmExploreTagOption(value.channel, value.group, value.tag);\n    var params = {\n        gender: qmExploreGender(value.channel),\n        tag_id: tag.id,\n        over: value.over,\n        words: value.words,\n        sort: value.sort\n    };\n    return qmTag(params.gender, params.tag_id, page, params.over, params.words, params.sort);\n}\n\n\nvar QMP_COMMENT_HTML = \"<!doctype html><html lang=\\\"zh-CN\\\"><head><meta charset=\\\"utf-8\\\"><meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1\\\"><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\">\\n<style>\\n:root{color-scheme:light dark;--bg:#fff;--fg:#202124;--muted:#73777d;--line:#eceef0;--accent:#496ba3;--soft:#f3f5f8} @media(prefers-color-scheme:dark){:root{--bg:#191b20;--fg:#e3e5e9;--muted:#a1a6b0;--line:#32363d;--accent:#a5c7ff;--soft:#242830}}\\n*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:16px\/1.65 system-ui,sans-serif;padding:0 20px 28px}header{position:sticky;top:0;background:var(--bg);padding:18px 0 12px;border-bottom:1px solid var(--line)}h1{font-size:19px;margin:0 0 10px}button{font:inherit;border:0;border-radius:20px;padding:7px 18px;background:var(--soft);color:var(--fg);cursor:pointer}button[aria-pressed=true]{background:var(--accent);color:var(--bg)}button:disabled{opacity:.55}nav{display:flex;gap:10px}blockquote{margin:18px 0;padding:12px 14px;background:var(--soft);border-left:3px solid var(--accent);font-size:14px;color:var(--muted);white-space:pre-wrap;overflow-wrap:anywhere}article{padding:16px 0;border-bottom:1px solid var(--line)}.name{font-size:14px;font-weight:650}.meta{font-size:12px;color:var(--muted);margin-top:8px}.text{white-space:pre-wrap;overflow-wrap:anywhere;margin-top:6px}#status{color:var(--muted);font-size:14px;margin:16px 0}#more{display:block;width:100%}small{font-weight:400;color:var(--muted);font-size:13px}\\n<\/style><\/head><body><header><h1>本段评论 <small id=\\\"count\\\"><\/small><\/h1><nav><button id=\\\"hot\\\" aria-pressed=\\\"true\\\">热门<\/button><button id=\\\"new\\\" aria-pressed=\\\"false\\\">最新<\/button><\/nav><\/header><blockquote id=\\\"quote\\\"><\/blockquote><main id=\\\"list\\\"><\/main><p id=\\\"status\\\" role=\\\"status\\\" aria-live=\\\"polite\\\">正在加载评论…<\/p><button id=\\\"more\\\">加载更多<\/button>\\n<script>\\n(function(){\\n  'use strict';\\n  var input=__QMP_INPUT__,cursor='',hot='1',busy=false,done=false,seen=Object.create(null),shown=0;\\n  var el=function(id){return document.getElementById(id)};\\n  el('quote').textContent=input.text;\\n  el('count').textContent=input.count+' 条';\\n  function node(tag,cls,text){var n=document.createElement(tag);n.className=cls;n.textContent=text;return n;}\\n  async function load(){\\n    if(busy||done)return;\\n    busy=true;el('more').disabled=true;el('hot').disabled=true;el('new').disabled=true;el('status').textContent='正在加载评论…';\\n    try{\\n      if(typeof window.qmpcRun!=='function')throw new Error('评论窗口桥接不可用，请使用阅读 MD3 3.26.16-beta.38');\\n      var request={bid:input.bid,cid:input.cid,pid:input.pid,cursor:cursor,hot:hot};\\n      var data=JSON.parse(await window.qmpcRun('qmpcPage.call(this,'+JSON.stringify(request)+')'));\\n      if(!data.ok)throw new Error(data.error||'评论加载失败');\\n      data.items.forEach(function(item){\\n        var key=item.id||item.name+'\\\\n'+item.content;if(seen[key])return;seen[key]=true;shown++;\\n        var card=document.createElement('article');card.appendChild(node('div','name',item.name));card.appendChild(node('div','text',item.content));\\n        card.appendChild(node('div','meta',[item.time,'赞 '+item.likes,item.replies+' 条回复'].join(' · ')));el('list').appendChild(card);\\n      });\\n      if(data.total!==null)el('count').textContent=data.total+' 条';\\n      done=!data.next||data.next===cursor;cursor=data.next;\\n      el('status').textContent=shown?(done?'已加载全部可见评论':'已加载 '+shown+' 条评论'):'本段暂无可见评论';\\n      el('more').textContent='加载更多';el('more').hidden=done;\\n    }catch(e){el('status').textContent='加载失败：'+(e.message||String(e));el('more').textContent='重试';el('more').hidden=false;}\\n    finally{busy=false;el('more').disabled=false;el('hot').disabled=false;el('new').disabled=false;}\\n  }\\n  function sort(value){if(busy||value===hot)return;hot=value;cursor='';done=false;shown=0;seen=Object.create(null);el('list').textContent='';el('more').hidden=false;el('hot').setAttribute('aria-pressed',String(hot==='1'));el('new').setAttribute('aria-pressed',String(hot==='0'));load();}\\n  el('hot').onclick=function(){sort('1')};el('new').onclick=function(){sort('0')};el('more').onclick=load;load();\\n})();\\n<\/script><\/body><\/html>\\n\";\n\/\/ MD3 beta.38 paragraph comments. Endpoint\/field reference: ah-fa-fa, source 7684.\n\/\/ Independently implemented read-only adapter; no guest registration or remote JS.\nfunction qmpcJava(ctx) {\n    return ctx && ctx.java ? ctx.java : java;\n}\nfunction qmpcSource(ctx) {\n    return ctx && ctx.source ? ctx.source : source;\n}\nfunction qmpcStatus(ctx, text) {\n    try { qmpcSource(ctx).put('qmpc_status', String(text)); } catch (ignored) {}\n}\nfunction qmpcRequest(ctx, path, params, post) {\n    var p = {}, h = JSON.parse(QM_HEADERS).headers;\n    Object.keys(params).forEach(function(k) { p[k] = String(params[k]); });\n    p.sign = qmSign(p);\n    var form = Object.keys(p).map(function(k) {return encodeURIComponent(k) + '=' + encodeURIComponent(p[k]);}).join('&');\n    var options = {headers:h}, url = 'https:\/\/api-cmnt.wtzw.com' + path;\n    if (post) {\n        options.method = 'POST'; options.body = form;\n        h['Content-Type'] = 'application\/x-www-form-urlencoded';\n    } else url += '?' + form;\n    var raw = String(qmpcJava(ctx).ajax(url + ',' + JSON.stringify(options), 12000) || '');\n    var result;\n    try { result = JSON.parse(raw); } catch (e) { throw new Error('七猫评论接口未返回有效 JSON'); }\n    if (!result || !result.data || typeof result.data !== 'object' || result.errors) {\n        throw new Error('七猫评论接口返回错误' + (result && result.code != null ? '（' + result.code + '）' : ''));\n    }\n    return result.data;\n}\nfunction qmpcBubble(ctx, bid, cid, pid, count, line) {\n    var label = count > 999 ? '999+' : String(count);\n    var svg = '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"68\" height=\"38\" viewBox=\"0 0 68 38\"><path d=\"M13 3h46a6 6 0 0 1 6 6v17a6 6 0 0 1-6 6H19l-9 4 2-8a6 6 0 0 1-5-6V9a6 6 0 0 1 6-6Z\" fill=\"none\" stroke=\"#7b8fae\" stroke-width=\"2\"\/><text x=\"36\" y=\"24\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"18\" fill=\"#7b8fae\">' + label + '<\/text><\/svg>';\n    var j = qmpcJava(ctx);\n    var payload = String(j.base64Encode(JSON.stringify({bid:bid,cid:cid,pid:pid,count:count,text:line})));\n    var click = 'qmpcOpen.call(this,' + JSON.stringify(payload) + ')';\n    \/\/ MD3 deliberately parses the trailing URL options JSON inside this src.\n    return '<img src=\"data:image\/svg+xml;base64,' + j.base64Encode(svg) + ',' + JSON.stringify({style:'TEXT',click:click}) + '\">';\n}\nfunction qmpcAnnotate(text, bid, cid, md5) {\n    var original = String(text), ctx = this;\n    try {\n        if (String(qmpcSource(ctx).get('qmpc_enabled')) === '0') return original;\n        if (\/<\\\/?(?:p|div|body|img|h[1-6])\\b\/i.test(original)) {\n            qmpcStatus(ctx, '本章是 HTML\/出版物正文，保留原排版，未添加段评'); return original;\n        }\n        var data = qmpcRequest(ctx, '\/api\/v1\/paragraph\/bubbles', {book_id:bid,chapters:JSON.stringify([{chapter_id:String(cid),chapter_md5:String(md5 || '')}])}, true);\n        if (!Array.isArray(data.chapters)) throw new Error('气泡响应缺少章节列表');\n        var chapterData = null;\n        data.chapters.forEach(function(c) { if (String(c.chapter_id) === String(cid)) chapterData = c; });\n        if (!chapterData || !Array.isArray(chapterData.bubbles)) throw new Error('气泡响应缺少当前章节');\n        var marks = {}, matched = 0;\n        chapterData.bubbles.forEach(function(b) {\n            var n = Number(b.c);\n            if (\/^[0-9a-f]{8}$\/i.test(String(b.p)) && isFinite(n) && n > 0) marks[String(b.p)] = Math.floor(n);\n        });\n        \/\/ Hash the original line before whitespace conversion or display filtering.\n        var result = original.split('\\n').map(function(line) {\n            if (!line.trim()) return line;\n            var pid = qmMd5(line).slice(0, 8), count = marks[pid];\n            if (!count) return line;\n            matched++;\n            return line + qmpcBubble(ctx, String(bid), String(cid), pid, count, line);\n        }).join('\\n');\n        qmpcStatus(ctx, '章节 ' + cid + '：已添加 ' + matched + ' 个段评气泡；接口返回 ' + Object.keys(marks).length + ' 个有评论的段落');\n        return result;\n    } catch (e) {\n        qmpcStatus(ctx, '段评加载失败，正文已保留：' + String(e.message || e));\n        return original;\n    }\n}\nfunction qmpcPage(input) {\n    try {\n        if (!input || !\/^\\d+$\/.test(String(input.bid)) || !\/^\\d+$\/.test(String(input.cid)) || !\/^[0-9a-f]{8}$\/i.test(String(input.pid))) throw new Error('段评标识无效');\n        var cursor = String(input.cursor || ''), hot = String(input.hot) === '0' ? '0' : '1';\n        var params = {book_id:input.bid,chapter_id:input.cid,paragraph_id:input.pid,hot:hot,check_cmt_id:''};\n        if (cursor) params.next_id = cursor;\n        else { params.exposed_ids = ''; params.need_common = hot; }\n        var data = qmpcRequest(this, '\/api\/v2\/paragraph\/' + (cursor ? 'more' : 'first'), params, !cursor);\n        var list = [], next = '', total = null;\n        if (cursor) {\n            if (!Array.isArray(data.comment_list) && data.comment_list !== null) throw new Error('评论响应缺少列表');\n            list = data.comment_list || []; next = String(data.next_id || '');\n        } else {\n            if (!data.time_zone && !data.hot_zone) throw new Error('评论响应缺少列表');\n            var hotZone = data.hot_zone || {}, timeZone = data.time_zone || {};\n            list = (hotZone.comment_list || []).concat(timeZone.comment_list || []);\n            next = String(timeZone.next_id || data.next_id || '');\n            var count = timeZone.comment_count != null ? timeZone.comment_count : hotZone.comment_count;\n            total = count == null ? null : String(count);\n        }\n        if (next === cursor) next = '';\n        var seen = {}, items = [];\n        list.forEach(function(item) {\n            var user = item.user_info || {}, id = String(item.comment_id || item.id || '');\n            if (id && seen[id]) return;\n            if (id) seen[id] = true;\n            items.push({id:id,name:String(user.nickname || '七猫用户'),content:String(item.content || ''),time:String(item.comment_time || ''),likes:String(item.like_count || '0'),replies:String(item.reply_count || '0')});\n        });\n        return JSON.stringify({ok:true,items:items,next:next,total:total});\n    } catch(e) { return JSON.stringify({ok:false,error:String(e.message || e),items:[],next:'',total:null}); }\n}\nfunction qmpcOpen(encoded) {\n    var j = qmpcJava(this), input = JSON.parse(String(j.base64Decode(encoded)));\n    \/\/ Protect the HTML script boundary even if the novel contains markup.\n    var safe = JSON.stringify(input).replace(\/<\/g, '\\\\u003c').replace(\/\\u2028\/g, '\\\\u2028').replace(\/\\u2029\/g, '\\\\u2029');\n    var html = QMP_COMMENT_HTML.replace('__QMP_INPUT__', function() { return safe; });\n    j.showBrowser('https:\/\/api-cmnt.wtzw.com\/', html, 'window.qmpcRun = run;', JSON.stringify({state:3,isHideable:true,heightPercentage:0.8,widthPercentage:1,skipCollapsed:true,dismissOnTouchOutside:true,expandedCornersRadius:16,webViewInitialScale:100}));\n}\nfunction qmpcSetEnabled(enabled) {\n    qmpcSource(this).put('qmpc_enabled', enabled ? '1' : '0');\n    qmpcJava(this).longToast('段评已' + (enabled ? '开启' : '关闭') + '，请返回阅读页刷新正文');\n}\nfunction qmpcShowStatus() {\n    qmpcJava(this).longToast(String(qmpcSource(this).get('qmpc_status') || '尚未加载段评，请进入章节并刷新正文'));\n}\n\n\/* Media transport adapted from 七猫四合一本地版（同人）, supplied by user. *\/\nvar QMA_NET = (function(qmMd5) {\nvar QM_APP_VERSION = '80400'\nvar QM_CHANNEL = 'qm-guanfang_lf'\nvar QM_APP_ID = 'com.kmxs.reader'\nvar QM_SECRET = 'd3dGiJc651gSQ8w1'\nvar QM_B64 = '+\/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nvar QM_PARAM_MAP = 'PXMUlErYWbdJ9saI0oy_HGitgNA8Fk3hfRqC4pmBOuc6Kx5T-2zSZ1VvjQ7DwnLe'\nvar QM_CONTENT_MAP = '5NhEilDnz01JC67qSmT89-FkGKor4stuHvBwxOILcdMeAfgPQRbyU2p3VjWX_YZa'\nfunction qmSourceOf(ctx) {\n  try { if (ctx && ctx.source) return ctx.source } catch (e) {}\n  try { if (typeof source != 'undefined') return source } catch (e2) {}\n  return null\n}\n\nfunction qmCallable(obj, name) {\n  try { return !!obj && typeof obj[name] == 'function' } catch (e) { return false }\n}\n\nfunction qmJavaOf(ctx) {\n  var candidate = null\n  try { candidate = ctx && ctx.java ? ctx.java : null } catch (e) {}\n  if (candidate && (qmCallable(candidate, 'ajax') || qmCallable(candidate, 'base64Encode') || qmCallable(candidate, 'showBrowser'))) return candidate\n  try { candidate = typeof java != 'undefined' ? java : null } catch (e2) {}\n  if (candidate && (qmCallable(candidate, 'ajax') || qmCallable(candidate, 'base64Encode') || qmCallable(candidate, 'showBrowser'))) return candidate\n  return null\n}\n\nfunction qmCacheOf(ctx) {\n  try { if (ctx && ctx.cache) return ctx.cache } catch (e) {}\n  try { if (typeof cache != 'undefined') return cache } catch (e2) {}\n  return null\n}\n\nfunction qmSign(value) {\n  return qmMd5(String(value) + QM_SECRET)\n}\n\nfunction qmUrlSign(data) {\n  var keys = Object.keys(data || {}).sort()\n  var text = ''\n  keys.forEach(function (key) { text += key + '=' + String(data[key]) })\n  return qmSign(text)\n}\n\nfunction qmMapChars(value, from, to) {\n  var out = ''\n  value = String(value)\n  for (var i = 0; i < value.length; i++) {\n    var index = from.indexOf(value.charAt(i))\n    out += index >= 0 ? to.charAt(index) : value.charAt(i)\n  }\n  return out\n}\n\nfunction qmParamEncode(value) {\n  var bytes = new Packages.java.lang.String(String(value)).getBytes('UTF-8')\n  var b64 = String(Packages.java.util.Base64.getEncoder().encodeToString(bytes)).replace(\/[=\\r\\n]\/g, '')\n  return qmMapChars(b64, QM_B64, QM_PARAM_MAP)\n}\n\nfunction qmContentLayerDecode(value) {\n  var b64 = qmMapChars(String(value), QM_CONTENT_MAP, QM_B64)\n  while (b64.length % 4) b64 += '='\n  try {\n    var bytes = Packages.java.util.Base64.getDecoder().decode(b64)\n    return String(new Packages.java.lang.String(bytes, 'UTF-8'))\n  } catch (e) {\n    return ''\n  }\n}\n\nfunction qmRandomHex(length) {\n  var value = String(Packages.java.util.UUID.randomUUID().toString()).replace(\/-\/g, '')\n  while (value.length < length) value += String(Packages.java.util.UUID.randomUUID().toString()).replace(\/-\/g, '')\n  return value.slice(0, length)\n}\n\nfunction qmPad(value, length) {\n  value = String(value)\n  while (value.length < length) value = '0' + value\n  return value\n}\n\nfunction qmDateStamp() {\n  var d = new Date()\n  return d.getFullYear() + qmPad(d.getMonth() + 1, 2) + qmPad(d.getDate(), 2) + qmPad(d.getHours(), 2) + qmPad(d.getMinutes(), 2) + qmPad(d.getSeconds(), 2)\n}\n\nfunction qmDevice() {\n  var runtimeSource = qmSourceOf(this)\n  var data\n  try { data = JSON.parse(String(runtimeSource && runtimeSource.get('qm_device') || '{}')) } catch (e) { data = {} }\n  if (!data.uuid) data.uuid = String(Packages.java.util.UUID.randomUUID().toString())\n  if (!data.device_id) data.device_id = qmDateStamp() + qmRandomHex(48)\n  if (!data.source_uid) data.source_uid = qmRandomHex(16)\n  if (!data.mac) data.mac = '02:' + qmRandomHex(10).match(\/..\/g).join(':')\n  data.gender = '0'\n  data.static_score = '0.4'\n  data.sys_ver = '12'\n  data.phone_level = 'M'\n  data.app_version = QM_APP_VERSION\n  data.brand = 'Redmi'\n  data.make = 'Redmi'\n  data.model = '23117RK66C'\n  if (runtimeSource) runtimeSource.put('qm_device', JSON.stringify(data))\n  return data\n}\n\nfunction qmBuildParams() {\n  var d = qmDevice.call(this)\n  var fields = [\n    ['gender', d.gender], ['static_score', d.static_score], ['uuid', d.uuid],\n    ['device-id', d.device_id], ['mac', d.mac], ['sourceuid', d.source_uid],\n    ['sys-ver', d.sys_ver], ['phone-level', d.phone_level], ['app_ver', d.app_version],\n    ['imei', ''], ['model', d.model], ['wlb-imei', ''], ['wlb-uid', d.source_uid],\n    ['client-id', d.source_uid], ['brand', d.brand], ['make', d.make], ['net_env', '1']\n  ]\n  var parts = fields.map(function (item) { return JSON.stringify(item[0]) + ':' + JSON.stringify(String(item[1])) })\n  return qmParamEncode('{' + parts.join(',') + '}')\n}\n\nfunction qmHeaderSign(token, qmParams) {\n  var values = {\n    'AUTHORIZATION': token || '', 'app-version': QM_APP_VERSION,\n    'application-id': QM_APP_ID, 'channel': QM_CHANNEL, 'is-white': '0',\n    'net-env': '1', 'platform': 'android', 'qm-params': qmParams, 'reg': ''\n  }\n  var order = ['AUTHORIZATION','app-version','application-id','channel','is-white','net-env','platform','qm-params','reg']\n  var text = ''\n  order.forEach(function (key) { text += key + '=' + values[key] })\n  return qmSign(text)\n}\n\nfunction qmDay() {\n  var d = new Date()\n  return d.getFullYear() + qmPad(d.getMonth() + 1, 2) + qmPad(d.getDate(), 2)\n}\n\nfunction qmHeaders(token) {\n  var runtimeSource = qmSourceOf(this)\n  var qmParams = qmBuildParams.call(this)\n  var now = Math.floor(Date.now() \/ 1000)\n  var uid = String(runtimeSource && runtimeSource.get('qm_uid') || '')\n  return {\n    'authorization': token || '', 'app-version': QM_APP_VERSION,\n    'application-id': QM_APP_ID, 'channel': QM_CHANNEL, 'is-white': '0',\n    'net-env': '1', 'platform': 'android', 'qm-params': qmParams, 'reg': '',\n    'sign': qmHeaderSign(token || '', qmParams), 'qm-uaf': qmDay() + '-' + uid,\n    'qm-it': String(now), 'qm-ii': String(now % 10000000000), 'no-permiss': '3',\n    'user-agent': 'webviewversion\/0',\n    'qm-request-id': qmSign(String(Date.now()) + qmRandomHex(8))\n  }\n}\n\nfunction qmParse(text) {\n  text = String(text == null ? '' : text).replace(\/^\\uFEFF\/, '').trim()\n  if (!text) throw new Error('七猫接口返回空响应')\n  var value\n  try {\n    value = JSON.parse(text)\n  } catch (e) {\n    throw new Error('七猫接口返回非 JSON，请检查网络或压缩响应')\n  }\n  if (value && value.v) {\n    var plain = qmContentLayerDecode(value.v)\n    if (plain) {\n      try { value = JSON.parse(plain) }\n      catch (e2) { throw new Error('七猫加密响应解码后不是 JSON') }\n    }\n  }\n  return value\n}\n\nfunction qmForm(data) {\n  return Object.keys(data).sort().map(function (key) {\n    return encodeURIComponent(key) + '=' + encodeURIComponent(String(data[key]))\n  }).join('&')\n}\n\nfunction qmParseJwt(token) {\n  try {\n    var part = String(token).split('.')[1].replace(\/-\/g, '+').replace(\/_\/g, '\/')\n    while (part.length % 4) part += '='\n    var bytes = Packages.java.util.Base64.getDecoder().decode(part)\n    return JSON.parse(String(new Packages.java.lang.String(bytes, 'UTF-8')))\n  } catch (e) { return {} }\n}\n\nfunction qmLogin(force) {\n  var runtimeSource = qmSourceOf(this)\n  var runtimeJava = qmJavaOf(this)\n  if (!runtimeSource || !runtimeJava) throw new Error('七猫运行上下文缺少 source\/java')\n  var token = String(runtimeSource.get('qm_token') || '')\n  var exp = Number(runtimeSource.get('qm_exp') || 0)\n  if (!force && token && exp > Math.floor(Date.now() \/ 1000) + 60) return token\n  var form = {cancell_check:'1', gender:'0'}\n  form.sign = qmUrlSign(form)\n  var headers = qmHeaders.call(this, '')\n  headers['content-type'] = 'application\/x-www-form-urlencoded'\n  var option = {method:'POST', headers:headers, body:qmForm(form)}\n  var response = qmParse(runtimeJava.ajax('https:\/\/xiaoshuo.wtzw.com\/api\/v1\/login\/tourist,' + JSON.stringify(option), 15000))\n  token = String(response.data && response.data.token || '')\n  if (!token) throw new Error('七猫游客登录失败')\n  var jwt = qmParseJwt(token)\n  var user = jwt.user || {}\n  runtimeSource.put('qm_token', token)\n  runtimeSource.put('qm_exp', String(jwt.exp || Math.floor(Date.now() \/ 1000) + 3600))\n  runtimeSource.put('qm_uid', String(user.uid || response.data.id || ''))\n  if (user.deviceId) {\n    var device = qmDevice.call(this)\n    device.device_id = String(user.deviceId)\n    runtimeSource.put('qm_device', JSON.stringify(device))\n  }\n  return token\n}\n\nfunction qmRequest(method, url, params, form) {\n  var runtimeJava = qmJavaOf(this)\n  if (!runtimeJava) throw new Error('七猫运行上下文缺少 java')\n  var token = qmLogin.call(this, false)\n  params = params || {}\n  var fullUrl = url\n  var option = {method:method, headers:qmHeaders.call(this, token)}\n  if (form) {\n    var body = {}\n    Object.keys(form).forEach(function (key) { body[key] = String(form[key]) })\n    body.sign = qmUrlSign(body)\n    option.headers['content-type'] = 'application\/x-www-form-urlencoded'\n    option.body = qmForm(body)\n  } else {\n    var query = {}\n    Object.keys(params).forEach(function (key) { query[key] = String(params[key]) })\n    var parts = Object.keys(query).sort().map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(query[key]) })\n    parts.push('sign=' + qmUrlSign(query))\n    fullUrl += '?' + parts.join('&')\n  }\n  return qmParse(runtimeJava.ajax(fullUrl + ',' + JSON.stringify(option), 15000))\n}\n\nfunction qmGet(url, params) { return qmRequest.call(this, 'GET', url, params, null) }\n\nfunction qmPostForm(url, form) { return qmRequest.call(this, 'POST', url, {}, form) }\n\nfunction qmRequestUrl(method, url, params, form) {\n  var token = qmLogin.call(this, false)\n  params = params || {}\n  var fullUrl = url\n  var option = {method:method, headers:qmHeaders.call(this, token)}\n  if (form) {\n    var body = {}\n    Object.keys(form).forEach(function (key) { body[key] = String(form[key]) })\n    body.sign = qmUrlSign(body)\n    option.headers['content-type'] = 'application\/x-www-form-urlencoded'\n    option.body = qmForm(body)\n  } else {\n    var query = {}\n    Object.keys(params).forEach(function (key) { query[key] = String(params[key]) })\n    var parts = Object.keys(query).sort().map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(query[key]) })\n    parts.push('sign=' + qmUrlSign(query))\n    fullUrl += '?' + parts.join('&')\n  }\n  return fullUrl + ',' + JSON.stringify(option)\n}\n\nfunction qmGetUrl(url, params) { return qmRequestUrl.call(this, 'GET', url, params, null) }\n\nfunction qmPostFormUrl(url, form) { return qmRequestUrl.call(this, 'POST', url, {}, form) }\n\nfunction qmUrlValue(url, key) {\n  var match = String(url || '').match(new RegExp('[?&#]' + key + '=([^&#,]+)'))\n  if (!match) return ''\n  try { return decodeURIComponent(match[1]) } catch (e) { return match[1] }\n}\n\nfunction qmItemId(x, mode) {\n  x = x || {}\n  if (mode == 'audio') return String(x.album_id || x.id || '')\n  if (mode == 'video') return String(x.playlet_id || x.id || '')\n  return String(x.id || x.book_id || '')\n}\n\nfunction qmIsCandidate(x, mode) {\n  x = x || {}\n  if (!qmItemId(x, mode) || !(x.title || x.original_title)) return false\n  if (mode == 'audio') {\n    return !!(x.album_id || x.data_type == 'album' || x.data_type == 'audio' ||\n      x.audio_num || x.chapter_list_desc || x.actors || x.thumb_image_link)\n  }\n  if (mode == 'video') {\n    return !!(x.playlet_id || x.data_type == 'playlet' || x.data_type == 'video' ||\n      x.total_num || x.play_num || x.video_url || x.play_list)\n  }\n  return !!(x.data_type == 'book' || x.book_type != null || x.reader_type != null ||\n    x.author || x.original_author || x.latest_chapter_id || x.latest_chapter_title || x.words_num)\n}\n\nfunction qmVideoState(kind) {\n  var runtimeCache = qmCacheOf(this)\n  if (!runtimeCache) return {pages:{}}\n  try {\n    var saved = runtimeCache.getFromMemory('qm-video-pages-' + kind)\n    var state = saved ? JSON.parse(String(saved)) : null\n    if (state && state.pages) return state\n  } catch (e) {}\n  return {pages:{}}\n}\n\nfunction qmResetVideoState(kind) {\n  var runtimeCache = qmCacheOf(this)\n  try { if (runtimeCache) runtimeCache.deleteMemory('qm-video-pages-' + kind) } catch (e) {}\n}\n\nfunction qmVideoUploadIds(kind, page) {\n  page = String(Number(page || 1) || 1)\n  if (page == '1') {\n    qmResetVideoState.call(this, kind)\n    return []\n  }\n  var state = qmVideoState.call(this, kind)\n  var ids = []\n  Object.keys(state.pages || {}).sort(function (a, b) { return Number(a) - Number(b) }).forEach(function (key) {\n    if (Number(key) < Number(page)) ids = ids.concat(state.pages[key] || [])\n  })\n  var seen = {}\n  return ids.filter(function (id) { id = String(id); if (!id || seen[id]) return false; seen[id] = 1; return true }).slice(-120).map(function (id) {\n    return {id:String(id),item_type:kind == 'animated' ? '14' : '11',ts:String(Date.now()),type:'0'}\n  })\n}\n\nfunction qmRememberVideoPage(kind, page, items) {\n  var runtimeCache = qmCacheOf(this)\n  if (!runtimeCache) return items\n  page = String(Number(page || 1) || 1)\n  var state = qmVideoState.call(this, kind)\n  var prior = {}\n  Object.keys(state.pages || {}).forEach(function (key) {\n    if (key != page) (state.pages[key] || []).forEach(function (id) { prior[String(id)] = 1 })\n  })\n  var current = []\n  var local = {}\n  var filtered = items.filter(function (item) {\n    var id = qmItemId(item, 'video')\n    if (!id || local[id] || prior[id]) return false\n    local[id] = 1\n    current.push(String(id))\n    return true\n  })\n  state.pages[page] = current\n  Object.keys(state.pages).forEach(function (key) { if (Number(key) < Number(page) - 8) delete state.pages[key] })\n  runtimeCache.putMemory('qm-video-pages-' + kind, JSON.stringify(state))\n  return filtered\n}\n\nfunction qmExploreMarker(url, kind, page) {\n  var comma = String(url).indexOf(',')\n  if (comma < 0) comma = String(url).length\n  return String(url).slice(0, comma) + '#qm_video=' + encodeURIComponent(kind) + '&qm_page=' + encodeURIComponent(String(page)) + String(url).slice(comma)\n}\n\nfunction qmExploreHomeUrl(mode, page, dramaKind) {\n  page = String(Number(page || 1) || 1)\n  if (mode == 'publish') {\n    return qmPostFormUrl.call(this, 'https:\/\/api-bc.wtzw.com\/api\/v7\/book-store', {\n      book_id:'',book_privacy:'1',count:'3',new_num:'4',new_user:'1',part:page == '1' ? '1' : '2',\n      publish_tab_ab_test:'1',read_preference:'0',refresh_state:page == '1' ? '4' : '7',scene_num:'5',\n      select_num:'6',tab_type:'3',upload_ids:'[]'\n    })\n  }\n  if (mode == 'audio') {\n    return qmPostFormUrl.call(this, 'https:\/\/api-bc.wtzw.com\/api\/v3\/album\/index', {\n      book_privacy:'1',cache_ver:'',read_preference:'0',real_person_num:'6',refresh_state:'4',upload_ids:'[]',user_state:''\n    })\n  }\n  if (dramaKind == 'drama') {\n    var dramaUrl = qmPostFormUrl.call(this, 'https:\/\/api-gw.wtzw.com\/playlet\/api\/v2\/list-by-tagId', {\n      page:page,playlet_privacy:'1',read_preference:'0',refresh_state:page == '1' ? '4' : '7',tag_id:'',upload_ids:JSON.stringify(qmVideoUploadIds.call(this, 'drama', page))\n    })\n    return qmExploreMarker(dramaUrl, 'drama', page)\n  }\n  var animatedUrl = qmPostFormUrl.call(this, 'https:\/\/api-gw.wtzw.com\/playlet\/api\/v1\/animated\/index', {\n    change_tag:'0',page:page,playlet_privacy:'1',read_preference:'0',refresh_state:page == '1' ? '4' : '7',tag_id:'',upload_ids:JSON.stringify(qmVideoUploadIds.call(this, 'animated', page))\n  })\n  return qmExploreMarker(animatedUrl, 'animated', page)\n}\n\nfunction qmExploreMediaSearchUrl(mode, keyword, page) {\n  page = String(Number(page || 1) || 1)\n  if (mode == 'audio') {\n    return qmGetUrl.call(this, 'https:\/\/api-bc.wtzw.com\/search\/v1\/words', {\n      extend:'',tab:'1',gender:'0',refresh_state:'8',track_id:qmDevice.call(this).source_uid + Date.now(),page:page,\n      book_id:'',book_privacy:'1',wd:String(keyword),read_preference:'4',is_short_story_user:'0'\n    })\n  }\n  return qmGetUrl.call(this, 'https:\/\/api-bc.wtzw.com\/search\/v1\/playlet', {\n    wd:String(keyword),read_preference:'4',page:page,track_id:qmDevice.call(this).source_uid + Date.now()\n  })\n}\nreturn {\nget:qmGet, post:qmPostForm, parse:qmParse, source:qmSourceOf, java:qmJavaOf,\nsearch:qmExploreMediaSearchUrl, home:qmExploreHomeUrl, login:qmLogin,\nremember:qmRememberVideoPage, candidate:qmIsCandidate, id:qmItemId, cache:qmCacheOf\n};\n})(qmMd5);\n\nvar QMA_PLAYER_HTML = \"<!doctype html>\\n<html lang=\\\"zh-CN\\\"><head><meta charset=\\\"utf-8\\\"><meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\\n<title>七猫剧场<\/title><style>\\n:root{color-scheme:light dark;font-family:system-ui,sans-serif;background:#11151f;color:#eef1fa}body{margin:0;padding:18px}h1{font-size:21px;margin:0 0 14px}video{display:block;width:100%;max-height:60vh;min-height:180px;background:#000;border-radius:14px}p{line-height:1.6;color:#b9c3d7}button,select{font:inherit;background:#26324c;color:#eef1fa;border:1px solid #4b5b7b;border-radius:10px;padding:11px;margin:5px 3px}button:disabled{opacity:.4}.controls{display:flex;flex-wrap:wrap;margin-top:12px}select{flex:1;min-width:140px}#status{min-height:3em;font-size:14px}\\n<\/style><\/head><body>\\n<h1 id=\\\"heading\\\">七猫剧场<\/h1><video id=\\\"video\\\" controls playsinline preload=\\\"metadata\\\"><\/video>\\n<div class=\\\"controls\\\"><button id=\\\"prev\\\">上一集<\/button><select id=\\\"episodes\\\" aria-label=\\\"选集\\\"><\/select><button id=\\\"next\\\">下一集<\/button><\/div>\\n<div class=\\\"controls\\\"><button id=\\\"retry\\\">刷新播放地址<\/button><button id=\\\"external\\\">外部播放器<\/button><\/div>\\n<p id=\\\"status\\\" role=\\\"status\\\">正在加载选集和播放地址…<\/p>\\n<script>\\n(function(){\\n  'use strict';\\n  var input=__QMA_INPUT__, current=String(input.episode), rows=[], busy=false, playable=false;\\n  function el(id){return document.getElementById(id)}\\n  var video=el('video');\\n  function encoded(){return btoa(JSON.stringify({qma:1,mode:input.mode,id:input.id,stage:'play',episode:current}));}\\n  async function run(method){\\n    if(typeof window.qmaRun!=='function')throw new Error('播放器与阅读器的连接尚未就绪，请关闭后重试');\\n    return await window.qmaRun('QMA.'+method+'.call(this,'+JSON.stringify(encoded())+')');\\n  }\\n  function buttons(){\\n    var i=rows.findIndex(function(x){return x.number===current});\\n    el('prev').disabled=busy||i<=0;el('next').disabled=busy||i<0||i>=rows.length-1;\\n    el('episodes').disabled=busy||!rows.length;el('retry').disabled=busy;el('external').disabled=busy||!playable;\\n  }\\n  async function load(){\\n    if(busy)return;\\n    busy=true;playable=false;buttons();video.pause();video.removeAttribute('src');video.load();\\n    el('heading').textContent='第'+current+'集';el('status').textContent='正在获取播放地址…';\\n    try{\\n      var raw=await run('playerData'),data=typeof raw==='string'?JSON.parse(raw):raw;\\n      if(!data||!data.ok)throw new Error(data&&data.error||'播放信息加载失败');\\n      rows=data.episodes||[];el('episodes').textContent='';\\n      rows.forEach(function(row){var option=document.createElement('option');option.value=row.number;option.textContent=row.title+(row.available?'':' · 暂无地址');el('episodes').appendChild(option)});\\n      el('episodes').value=current;\\n      if(!data.url){el('status').textContent='本集暂无可播放地址，可以选择其他集或稍后刷新。';return;}\\n      if(!\/^https?:\\\\\/\\\\\/\/i.test(data.url))throw new Error('播放地址格式无效');\\n      video.src=data.url;playable=true;el('status').textContent='点击画面中的播放按钮开始观看。';\\n    }catch(e){el('status').textContent='加载失败：'+(e.message||String(e));}\\n    finally{busy=false;buttons()}\\n  }\\n  function move(delta){if(busy)return;var i=rows.findIndex(function(x){return x.number===current});if(rows[i+delta]){current=rows[i+delta].number;load()}}\\n  el('prev').onclick=function(){move(-1)};el('next').onclick=function(){move(1)};\\n  el('episodes').onchange=function(){current=this.value;load()};el('retry').onclick=load;\\n  el('external').onclick=async function(){if(busy)return;busy=true;buttons();try{video.pause();await run('external');el('status').textContent='已请求打开外部播放器。'}catch(e){el('status').textContent='打开失败：'+(e.message||e)}finally{busy=false;buttons()}};\\n  video.onerror=function(){if(playable)el('status').textContent='网页播放失败，请刷新地址或尝试外部播放器。';};\\n  window.addEventListener('pagehide',function(){video.pause();video.removeAttribute('src');video.load()});\\n  load();\\n})();\\n<\/script><\/body><\/html>\\n\";\n\/* MD3 beta.45 adapter. All media links store IDs, never expiring credentials. *\/\nvar QMA = (function () {\n  function bridge(ctx) { return QMA_NET.java(ctx); }\n  function state(ctx, message) {\n    var s = QMA_NET.source(ctx);\n    if (s) s.put('qma_status', String(message));\n  }\n  function id(value) {\n    value = String(value == null ? '' : value);\n    if (!\/^\\d+$\/.test(value)) throw new Error('媒体标识无效');\n    return value;\n  }\n  function dataUrl(ctx, value) {\n    value.qma = 1;\n    return 'data:;base64,' + bridge(ctx).base64Encode(JSON.stringify(value)) + ',{\"type\":\"qma\"}';\n  }\n  function meta(url, text) {\n    var match = String(url || '').match(\/^data:;base64,([A-Za-z0-9+\/=]+)\/);\n    try {\n      if (match) {\n        var obj = JSON.parse(String(bridge(this).base64Decode(match[1])));\n        if (obj && obj.qma === 1) return obj;\n      }\n    } catch (ignored) {}\n    \/\/ MD3 StrResponse maps data: URLs to localhost; recover from the hex body.\n    try {\n      var raw = String(text == null ? '' : text), value;\n      if (\/^(?:[0-9a-f]{2})+$\/i.test(raw)) raw = String(bridge(this).hexDecodeToString(raw));\n      value = JSON.parse(raw);\n      if (value && value.qma === 1) return value;\n      if (value && value.qma_meta && value.qma_meta.qma === 1) return value.qma_meta;\n    } catch (ignored2) {}\n    try {\n      var b = this && this.book ? this.book : typeof book !== 'undefined' ? book : null;\n      var saved = b && b.getVariable ? String(b.getVariable('qma_meta') || '') : '';\n      if (saved) return JSON.parse(saved);\n    } catch (ignored3) {}\n    return null;\n  }\n  function payload(value) {\n    var p = typeof value === 'string' ? QMA_NET.parse(value) : value;\n    if (!p || !p.data || p.errors || (p.code != null && Number(p.code) !== 0 && Number(p.code) !== 200)) {\n      throw new Error('七猫媒体接口未返回有效数据' + (p && p.code != null ? '（' + p.code + '）' : ''));\n    }\n    return p.data;\n  }\n  function plain(value) { return String(value == null ? '' : value).replace(\/<[^>]*>\/g, '').replace(\/&amp;\/g, '&').trim(); }\n  function http(value) {\n    value = String(value || '').trim();\n    if (!\/^https?:\\\/\\\/[^\\s<>\"']+$\/i.test(value)) throw new Error('本集暂无可播放地址');\n    return value;\n  }\n  function mark(ctx, mode, mediaId, stage, episode) {\n    var m = {mode:mode,id:id(mediaId),stage:stage};\n    if (episode != null) m.episode = id(episode);\n    return dataUrl(ctx, m);\n  }\n  function novel(x) {\n    return {title:x.original_title || x.title,author:x.original_author || x.author || '',\n      cover:x.image_link || '',intro:x.intro || '',kind:x.ptags || '',words:x.words_num || '',\n      last:x.latest_chapter_title || '',url:qmDetail(id(x.id))};\n  }\n  function media(ctx, x, mode) {\n    if (!QMA_NET.candidate(x, mode === 'audio' ? 'audio' : 'video')) return null;\n    var mediaId = id(QMA_NET.id(x, mode === 'audio' ? 'audio' : 'video'));\n    var actual = mode === 'audio' ? 'audio' : String(x.content_type || '') === '2' ? 'animated' : 'drama';\n    var kind = {audio:'听书',drama:'短剧',animated:'漫剧'}[actual];\n    return {title:plain(x.original_title || x.title),author:mode === 'audio' ? plain(x.author || x.actors || '七猫听书') : '七猫' + kind,\n      cover:x.image_link || x.thumb_image_link || '',intro:plain(x.intro || x.description || x.sub_title),kind:kind,words:'',\n      last:x.chapter_list_desc || ((x.total_num || x.audio_num) ? '共 ' + (x.total_num || x.audio_num) + ' 集' : ''),\n      url:mark(ctx,actual,mediaId,'info')};\n  }\n  function collect(ctx, node, mode, output, seen, depth) {\n    if (!node || depth > 8) return;\n    if (Array.isArray(node)) { node.forEach(function(x) { collect(ctx,x,mode,output,seen,depth+1); }); return; }\n    if (typeof node !== 'object') return;\n    if (QMA_NET.candidate(node,mode === 'audio' ? 'audio' : 'video')) {\n      var item = media(ctx,node,mode);\n      if (item && !seen[item.url]) { seen[item.url] = true; output.push(item); }\n    }\n    Object.keys(node).forEach(function(k) {\n      if (!\/^(meta|stat_params|track_id|tag_items|category_list|rank_list)$\/.test(k)) collect(ctx,node[k],mode,output,seen,depth+1);\n    });\n  }\n  function searchUrl(key, page) {\n    return dataUrl(this,{stage:'search',key:String(key || '').trim(),page:Math.max(1,Number(page)||1)});\n  }\n  function search(ctx, request) {\n    var keyword = request.key, mode = 'novel';\n    var prefix = keyword.match(\/^(小说|听书|有声|短剧|漫剧|视频|全部|n|a|v)\\s*[:：]\\s*(.*)$\/i);\n    if (prefix) {\n      mode = {'小说':'novel','听书':'audio','有声':'audio','短剧':'drama','漫剧':'animated','视频':'video','全部':'all',n:'novel',a:'audio',v:'video'}[prefix[1].toLowerCase()];\n      keyword = prefix[2].trim();\n    }\n    if (!keyword) throw new Error('请输入要搜索的名称，例如 听书:三国');\n    var modes = mode === 'all' ? ['novel','audio','video'] : [mode], out = [], failures = [], successes = 0;\n    modes.forEach(function(selected) {\n      try {\n        var rows;\n        if (selected === 'novel') {\n          rows = payload(String(bridge(ctx).ajax(qmSearch(keyword,request.page),15000))).books;\n          if (!Array.isArray(rows)) throw new Error('小说搜索响应缺少书籍列表');\n          out = out.concat(rows.filter(function(x){return x.id && (x.title || x.original_title);}).map(novel));\n        } else {\n          var url = QMA_NET.search.call(ctx,selected === 'audio' ? 'audio' : 'video',keyword,request.page);\n          var data = payload(String(bridge(ctx).ajax(url,15000))), items = [];\n          collect(ctx,data,selected,items,{},0);\n          out = out.concat(items.filter(function(x){return selected !== 'animated' && selected !== 'drama' || x.kind === (selected === 'animated' ? '漫剧' : '短剧');}));\n        }\n        successes++;\n      } catch (e) { failures.push(selected + '：' + String(e.message || e)); }\n    });\n    state(ctx,failures.length ? '部分搜索失败：' + failures.join('；') : '搜索完成，共 ' + out.length + ' 项');\n    if (!successes) throw new Error(failures.join('；'));\n    return out;\n  }\n  function list(text, url) {\n    var m = meta.call(this,url,text), ctx = this;\n    if (!m) return (payload(String(text)).books || []).map(novel);\n    if (m.stage === 'search') return search(ctx,m);\n    if (m.stage !== 'explore') throw new Error('无效的列表入口');\n    var out = [], d;\n    if (m.mode === 'audio') {\n      var filters = QMA_DISC.audioNormalize(m.filters || {});\n      if (QMA_DISC.audioEnded(ctx,filters,m.page)) return [];\n      d = payload(QMA_NET.get.call(ctx,'https:\/\/api-bc.wtzw.com\/api\/v1\/album\/category-list',QMA_DISC.audioParams(filters,m.page)));\n      if (!Array.isArray(d.books)) throw new Error('听书分类响应缺少列表');\n      QMA_DISC.audioPages(ctx,filters,d.meta);\n      collect(ctx,d.books,'audio',out,{},0);\n    }\n    else {\n      var params = QMA_DISC.videoParams(ctx,m);\n      if (!params) return [];\n      var path = m.mode === 'animated' ? '\/playlet\/api\/v1\/animated\/index' : '\/playlet\/api\/v2\/list-by-tagId';\n      d = payload(QMA_NET.post.call(ctx,'https:\/\/api-gw.wtzw.com'+path,params));\n      if (!Array.isArray(d.playlet_list)) throw new Error('视频首页响应缺少剧集列表');\n      var contentType = m.mode === 'animated' ? '2' : '1';\n      var raw = d.playlet_list.filter(function(x){return String(x.content_type) === contentType;});\n      raw = QMA_DISC.videoRows(ctx,m,raw,d.next_page);\n      out = raw.map(function(x){return media(ctx,x,m.mode);}).filter(function(x){return !!x;});\n    }\n    state(ctx,'发现页已加载 ' + out.length + ' 项');\n    return out;\n  }\n  function exploreUrl(mode, page, selection) {\n    if (!\/^(audio|drama|animated)$\/.test(mode)) throw new Error('媒体分类无效');\n    var request = {stage:'explore',mode:mode,page:Math.max(1,Math.floor(Number(page)||1))};\n    if (mode === 'audio') request.filters = QMA_DISC.audioNormalize(selection || {});\n    else request.tag = QMA_DISC.videoTag(mode,selection == null ? '0' : String(selection));\n    return dataUrl(this,request);\n  }\n  function getInfo(ctx,m) {\n    var d = payload(QMA_NET.get.call(ctx,m.mode === 'audio' ? 'https:\/\/api-bc.wtzw.com\/api\/v2\/album\/detail' : 'https:\/\/api-gw.wtzw.com\/playlet\/api\/info',\n      m.mode === 'audio' ? {album_id:id(m.id)} : {playlet_id:id(m.id)}));\n    var raw = m.mode === 'audio' ? d.book || d : d.playlet || d.playlet_info || d;\n    if (!raw.title) throw new Error('媒体详情缺少标题，请刷新后重试');\n    return {raw:raw,data:d};\n  }\n  function info(url, text) {\n    var m = meta.call(this,url,text), raw = getInfo(this,m).raw;\n    var kind = m.mode === 'audio' ? '听书' : m.mode === 'animated' || String(raw.content_type) === '2' ? '漫剧' : '短剧';\n    var b = this && this.book ? this.book : typeof book !== 'undefined' ? book : null;\n    if (b) {\n      b.type = m.mode === 'audio' ? 32 : 8;\n      if (b.putVariable) {\n        b.putVariable('qma_meta',JSON.stringify(m));\n        b.putVariable('qma_card_title',JSON.stringify({mode:m.mode,id:m.id,title:raw.title}));\n      }\n    }\n    return {qma_meta:m,id:m.id,title:raw.title,author:m.mode === 'audio' ? raw.author || raw.actors || '七猫听书' : '七猫' + kind,\n      image_link:raw.image_link || raw.thumb_image_link || '',intro:plain(raw.intro || raw.description || raw.desc || raw.summary) || '七猫' + kind,\n      book_tag_list:[{title:kind}],latest_chapter_title:raw.chapter_list_desc || '共 ' + (raw.total_num || raw.audio_num || '?') + ' 集',\n      toc_url:mark(this,m.mode,m.id,'toc'),words_num:'',update_time:0,statement:'七猫'};\n  }\n  function videos(ctx,m) {\n    var d = payload(QMA_NET.get.call(ctx,'https:\/\/api-gw.wtzw.com\/playlet\/api\/info',{playlet_id:id(m.id)}));\n    var rows = d.play_list || (d.playlet || d.playlet_info || {}).play_list;\n    if (!Array.isArray(rows)) throw new Error('视频响应缺少选集列表');\n    var seen = {}, items = [];\n    rows.forEach(function(x,index){\n      var n = String(x.sort == null ? index+1 : x.sort);\n      if (!\/^\\d+$\/.test(n) || seen[n]) return;\n      seen[n] = true;\n      items.push({number:n,title:'第'+n+'集',duration:x.duration || '',url:String(x.video_url || x.video_url_h265 || '')});\n    });\n    return items.sort(function(a,b){return Number(a.number)-Number(b.number);});\n  }\n  function toc(url, text) {\n    var m = meta.call(this,url,text), ctx = this;\n    var b = ctx && ctx.book ? ctx.book : typeof book !== 'undefined' ? book : null;\n    \/\/ MD3 resets the book type from bookSourceType before every TOC refresh.\n    if (b) b.type = m.mode === 'audio' ? 32 : 8;\n    if (m.mode === 'audio') {\n      var d = payload(QMA_NET.get.call(ctx,'https:\/\/api-ks.wtzw.com\/api\/v1\/album\/chapter-list',{album_id:id(m.id),source:'0'}));\n      var rows = d.chapter_list || d.list || d.chapters;\n      if (!Array.isArray(rows)) throw new Error('听书响应缺少章节列表');\n      return rows.map(function(x,index){\n        return {title:x.title || x.chapter_title || '第'+(index+1)+'集',url:mark(ctx,'audio',m.id,'play',x.id || x.chapter_id),info:'时长 '+(x.duration || '-')+' 秒'};\n      });\n    }\n    return videos(ctx,m).map(function(x){return {title:x.title,url:mark(ctx,m.mode,m.id,'play',x.number),info:'时长 '+(x.duration || '-')+' 秒'};});\n  }\n  function xml(value) {\n    var escaped={'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&apos;'};\n    return String(value).replace(\/[&<>\"']\/g,function(c){return escaped[c];});\n  }\n  function cardTitle(ctx,m) {\n    var b = ctx && ctx.book ? ctx.book : typeof book !== 'undefined' ? book : null;\n    try { if (b && b.name && String(b.name).trim()) return String(b.name).trim(); } catch (ignored) {}\n    try {\n      var saved=JSON.parse(String(b && b.getVariable ? b.getVariable('qma_card_title') || '{}' : '{}'));\n      if (saved.mode===m.mode && String(saved.id)===String(m.id) && saved.title) return String(saved.title);\n    } catch (ignored2) {}\n    return '七猫漫剧';\n  }\n  function titleLines(value) {\n    value=String(value).replace(\/[\\x00-\\x1f]\/g,' ').trim();\n    var lines=[],line='',width=0;\n    for(var i=0;i<value.length;i++) {\n      var c=value.charAt(i),code=value.charCodeAt(i);\n      if(code>=0xd800 && code<=0xdbff && i+1<value.length && value.charCodeAt(i+1)>=0xdc00 && value.charCodeAt(i+1)<=0xdfff)c+=value.charAt(++i);\n      var unit=code<128?0.6:1;\n      if(width+unit>20) {\n        if(lines.length===1){lines.push(line+'…');return lines;}\n        lines.push(line);line='';width=0;\n      }\n      line+=c;width+=unit;\n    }\n    if(line)lines.push(line);\n    return lines.length?lines:['七猫漫剧'];\n  }\n  function animatedCard(ctx,m) {\n    var lines=titleLines(cardTitle(ctx,m)),episodeY=134+(lines.length-1)*38,buttonY=episodeY+24,height=buttonY+158;\n    var svg='<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"720\" height=\"'+height+'\" viewBox=\"0 0 720 '+height+'\" font-family=\"sans-serif\">'+\n      '<rect x=\"1\" y=\"1\" width=\"718\" height=\"'+(height-2)+'\" rx=\"28\" fill=\"#FAFCFF\" stroke=\"#DCE5F2\" stroke-width=\"2\"\/>'+\n      '<rect x=\"36\" y=\"26\" width=\"62\" height=\"26\" rx=\"8\" fill=\"#E9EEFF\"\/>'+\n      '<text x=\"67\" y=\"45\" text-anchor=\"middle\" font-size=\"16\" fill=\"#4C63AE\">漫剧<\/text>'+\n      '<text x=\"684\" y=\"45\" text-anchor=\"end\" font-size=\"16\" fill=\"#7A879B\">七猫 · 视频<\/text>';\n    lines.forEach(function(line,index){svg+='<text x=\"36\" y=\"'+(96+index*38)+'\" font-size=\"30\" font-weight=\"700\" fill=\"#202C42\">'+xml(line)+'<\/text>';});\n    svg+='<text x=\"36\" y=\"'+episodeY+'\" font-size=\"20\" fill=\"#6D7B91\">第 '+id(m.episode)+' 集<\/text>'+\n      '<rect x=\"36\" y=\"'+buttonY+'\" width=\"648\" height=\"72\" rx=\"18\" fill=\"#4663BA\"\/>'+\n      '<path d=\"M72 '+(buttonY+23)+' L72 '+(buttonY+49)+' L94 '+(buttonY+36)+' Z\" fill=\"#FFFFFF\"\/>'+\n      '<text x=\"360\" y=\"'+(buttonY+45)+'\" text-anchor=\"middle\" font-size=\"25\" font-weight=\"700\" fill=\"#FFFFFF\">外部播放<\/text>'+\n      '<text x=\"360\" y=\"'+(buttonY+106)+'\" text-anchor=\"middle\" font-size=\"18\" fill=\"#6D7B91\">确认跳转后，选择 VLC 等视频播放器<\/text>'+\n      '<text x=\"360\" y=\"'+(buttonY+132)+'\" text-anchor=\"middle\" font-size=\"18\" fill=\"#8B96A7\">换集请返回目录 · 点击时刷新播放地址<\/text><\/svg>';\n    var encoded=bridge(ctx).base64Encode(JSON.stringify(m));\n    var opts={style:'full',click:'QMA.external.call(this,'+JSON.stringify(String(encoded))+')'};\n    \/\/ A single image fixes typography independently of the reader's body font.\n    return '<img src=\"data:image\/svg+xml;base64,'+bridge(ctx).base64Encode(svg)+','+JSON.stringify(opts)+'\">';\n  }\n  function content(url, text) {\n    var m = meta.call(this,url,text);\n    if (!m || m.stage !== 'play') throw new Error('无效的媒体章节');\n    if (m.mode === 'audio') {\n      var d = payload(QMA_NET.get.call(this,'https:\/\/api-ks.wtzw.com\/api\/v1\/album\/info',{album_id:id(m.id),chapter_id:id(m.episode),new_user:'0'}));\n      var voices = d.voice_list || [], voice = voices.filter(function(x){return \/^https?:\\\/\\\/\/i.test(String(x.voice_url || ''));})[0];\n      return http(voice && voice.voice_url);\n    }\n    if (m.mode === 'animated') return animatedCard(this,m);\n    var encoded = bridge(this).base64Encode(JSON.stringify(m));\n    var svg = '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"540\" height=\"120\" viewBox=\"0 0 540 120\"><rect width=\"540\" height=\"120\" rx=\"24\" fill=\"#3957a5\"\/><path d=\"M42 32L42 88L84 60Z\" fill=\"white\"\/><text x=\"110\" y=\"72\" font-size=\"30\" fill=\"white\">点击播放 · 第'+id(m.episode)+'集<\/text><\/svg>';\n    var opts = {style:'full',click:'QMA.open.call(this,'+JSON.stringify(String(encoded))+')'};\n    var tip = '点击上方按钮打开播放器，可切换选集或使用外部播放器。';\n    return '第'+m.episode+'集\\n\\n<img src=\"data:image\/svg+xml;base64,'+bridge(this).base64Encode(svg)+','+JSON.stringify(opts)+'\">\\n\\n'+tip;\n  }\n  function decode(ctx,encoded) {\n    var m = JSON.parse(String(bridge(ctx).base64Decode(String(encoded))));\n    if (!\/^(drama|animated)$\/.test(m.mode)) throw new Error('无效的视频类型');\n    id(m.id); id(m.episode);\n    return m;\n  }\n  function playerData(encoded) {\n    try {\n      var m = decode(this,encoded), rows = videos(this,m), current = null;\n      rows.forEach(function(x){if (x.number === String(m.episode)) current = x;});\n      if (!current) throw new Error('目录中没有本集，请返回刷新目录');\n      var address = '';\n      try { address = http(current.url); } catch (e) {}\n      state(this,address ? '已解析第'+m.episode+'集播放地址' : '第'+m.episode+'集暂无可播放地址');\n      return JSON.stringify({ok:true,url:address,title:current.title,episode:m.episode,\n        episodes:rows.map(function(x){return {number:x.number,title:x.title,available:!!x.url};})});\n    } catch (e) { state(this,String(e.message || e)); return JSON.stringify({ok:false,error:String(e.message || e)}); }\n  }\n  function open(encoded) {\n    var m = decode(this,encoded);\n    \/\/ Cached animated chapters may still contain the older QMA.open button.\n    if (m.mode === 'animated') return external.call(this,encoded);\n    var safe = JSON.stringify({encoded:String(encoded),mode:m.mode,id:m.id,episode:m.episode}).replace(\/<\/g,'\\\\u003c');\n    var html = QMA_PLAYER_HTML.replace('__QMA_INPUT__',function(){return safe;});\n    bridge(this).showBrowser('https:\/\/api-gw.wtzw.com\/',html,'window.qmaRun = run;',JSON.stringify({state:3,isHideable:true,heightPercentage:0.94,widthPercentage:1,skipCollapsed:true,hardwareAccelerated:true}));\n  }\n  function external(encoded) {\n    var data = JSON.parse(playerData.call(this,encoded));\n    if (!data.ok || !data.url) throw new Error(data.error || '本集暂无可播放地址');\n    bridge(this).openUrl(http(data.url),'video\/*');\n    return true;\n  }\n  function help() { bridge(this).longToast('发现页长按书源→登录，可选择小说、听书、漫剧、短剧。切换后返回发现，收起再展开；必要时长按→刷新。听书支持分类、标签、状态、排序。默认搜索小说；其他内容输入 听书:名称 \/ 短剧:名称 \/ 漫剧:名称 \/ 全部:名称。'); }\n  function status() { bridge(this).longToast(String(QMA_NET.source(this).get('qma_status') || '尚未请求媒体接口')); }\n  function login() { try { QMA_NET.login.call(this,false); state(this,'媒体游客登录成功'); } catch(e) { state(this,String(e.message || e)); } status.call(this); }\n  function reset() {\n    var s = QMA_NET.source(this);\n    ['qm_token','qm_exp','qm_uid','qm_device'].forEach(function(k){s.put(k,'');});\n    state(this,'媒体游客身份已重置，下次媒体请求重新登录'); status.call(this);\n  }\n  return {meta:meta,searchUrl:searchUrl,list:list,exploreUrl:exploreUrl,info:info,toc:toc,content:content,\n    open:open,playerData:playerData,external:external,help:help,status:status,login:login,reset:reset};\n})();\n\nvar QMA_CATALOG = {\"audio\":[{\"name\":\"总裁\",\"id\":\"682\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"现代言情\",\"772\"],[\"都市\",\"758\"],[\"甜宠\",\"21\"],[\"HE\",\"581\"],[\"优质网文\",\"1388\"],[\"1V1\",\"585\"],[\"霸道\",\"239\"],[\"虐恋\",\"16\"],[\"先婚后爱\",\"524\"],[\"萌宝\",\"356\"],[\"追妻火葬场\",\"753\"],[\"打脸虐渣\",\"739\"],[\"腹黑\",\"789\"]]},{\"name\":\"异能\",\"id\":\"644\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"都市\",\"758\"],[\"爽文\",\"570\"],[\"逆袭\",\"400\"],[\"优质网文\",\"1388\"],[\"热血\",\"1\"],[\"系统\",\"782\"],[\"金手指\",\"756\"],[\"扮猪吃虎\",\"794\"],[\"医生\",\"156\"],[\"多女主\",\"945\"],[\"强者回归\",\"402\"],[\"赘婿\",\"36\"],[\"重生\",\"779\"],[\"无敌流\",\"305\"]]},{\"name\":\"悬疑\",\"id\":\"27\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"奇闻异事\",\"769\"],[\"恐怖\",\"567\"],[\"奇闻怪谈\",\"1396\"],[\"推理\",\"668\"],[\"都市\",\"758\"],[\"风水\",\"437\"],[\"悬疑探案\",\"1390\"],[\"侦探\",\"592\"],[\"探险\",\"697\"],[\"智商在线\",\"806\"],[\"烧脑\",\"571\"],[\"言情\",\"745\"],[\"第一人称\",\"977\"],[\"盗墓\",\"327\"]]},{\"name\":\"宫斗\",\"id\":\"343\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"甜宠\",\"21\"],[\"穿越\",\"373\"],[\"优质网文\",\"1388\"],[\"权谋\",\"785\"],[\"女强\",\"620\"],[\"重生\",\"779\"],[\"爽文\",\"570\"],[\"宅斗\",\"342\"],[\"1V1\",\"585\"],[\"皇帝\",\"62\"],[\"皇后\",\"106\"]]},{\"name\":\"历史\",\"id\":\"669\",\"tags\":[[\"全部\",\"-99\"],[\"人文社科\",\"796\"],[\"传记\",\"591\"],[\"有声小说\",\"923\"],[\"近代\",\"728\"],[\"穿越\",\"373\"],[\"文化\",\"685\"],[\"历史万象\",\"1391\"],[\"社会\",\"723\"],[\"爽文\",\"570\"],[\"古代\",\"604\"],[\"评书\",\"927\"],[\"文学艺术\",\"795\"],[\"曲艺\",\"925\"],[\"热血\",\"1\"],[\"大明\",\"636\"]]},{\"name\":\"穿越\",\"id\":\"373\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"爽文\",\"570\"],[\"甜宠\",\"21\"],[\"优质网文\",\"1388\"],[\"种田\",\"317\"],[\"历史\",\"669\"],[\"女强\",\"620\"],[\"逆袭\",\"400\"],[\"玄幻\",\"717\"],[\"历史万象\",\"1391\"],[\"1V1\",\"585\"],[\"系统\",\"782\"]]},{\"name\":\"年代\",\"id\":\"672\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"现代言情\",\"772\"],[\"言情\",\"745\"],[\"重生\",\"779\"],[\"现实题材\",\"12\"],[\"优质网文\",\"1388\"],[\"甜宠\",\"21\"],[\"致富\",\"492\"],[\"都市\",\"758\"],[\"爽文\",\"570\"],[\"乡村\",\"599\"],[\"励志\",\"786\"],[\"种田\",\"317\"],[\"打脸虐渣\",\"739\"],[\"1V1\",\"585\"]]},{\"name\":\"乡村\",\"id\":\"599\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"都市\",\"758\"],[\"爽文\",\"570\"],[\"逆袭\",\"400\"],[\"优质网文\",\"1388\"],[\"奇闻异事\",\"769\"],[\"现实题材\",\"12\"],[\"恐怖\",\"567\"],[\"悬疑\",\"27\"],[\"奇闻怪谈\",\"1396\"],[\"年代\",\"672\"],[\"多女主\",\"945\"],[\"医生\",\"156\"],[\"风水\",\"437\"],[\"致富\",\"492\"]]},{\"name\":\"重生\",\"id\":\"779\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"爽文\",\"570\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"甜宠\",\"21\"],[\"都市\",\"758\"],[\"优质网文\",\"1388\"],[\"现代言情\",\"772\"],[\"复仇\",\"790\"],[\"年代\",\"672\"],[\"打脸虐渣\",\"739\"],[\"女强\",\"620\"],[\"逆袭\",\"400\"],[\"1V1\",\"585\"]]},{\"name\":\"种田\",\"id\":\"317\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"穿越\",\"373\"],[\"优质网文\",\"1388\"],[\"致富\",\"492\"],[\"甜宠\",\"21\"],[\"爽文\",\"570\"],[\"女强\",\"620\"],[\"1V1\",\"585\"],[\"随身空间\",\"345\"],[\"重生\",\"779\"],[\"金手指\",\"756\"],[\"HE\",\"581\"]]},{\"name\":\"武侠\",\"id\":\"371\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"热血\",\"1\"],[\"古代\",\"604\"],[\"评书\",\"927\"],[\"曲艺\",\"925\"],[\"历史\",\"669\"],[\"爽文\",\"570\"],[\"言情\",\"745\"],[\"古代言情\",\"773\"],[\"逆袭\",\"400\"],[\"杀伐果断\",\"202\"],[\"智商在线\",\"806\"],[\"优质网文\",\"1388\"],[\"仙侠\",\"589\"],[\"玄幻\",\"717\"]]},{\"name\":\"仙侠\",\"id\":\"589\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"修仙\",\"659\"],[\"爽文\",\"570\"],[\"热血\",\"1\"],[\"穿越\",\"373\"],[\"玄幻\",\"717\"],[\"逆袭\",\"400\"],[\"燃向网文\",\"1393\"],[\"优质网文\",\"1388\"],[\"杀伐果断\",\"202\"],[\"洪荒\",\"559\"],[\"系统\",\"782\"],[\"扮猪吃虎\",\"794\"],[\"言情\",\"745\"],[\"幻想言情\",\"774\"]]},{\"name\":\"女强\",\"id\":\"620\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"甜宠\",\"21\"],[\"爽文\",\"570\"],[\"穿越\",\"373\"],[\"优质网文\",\"1388\"],[\"现代言情\",\"772\"],[\"1V1\",\"585\"],[\"重生\",\"779\"],[\"都市\",\"758\"],[\"种田\",\"317\"],[\"豪门总裁\",\"682\"],[\"打脸虐渣\",\"739\"]]},{\"name\":\"游戏\",\"id\":\"714\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"爽文\",\"570\"],[\"网游\",\"740\"],[\"都市\",\"758\"],[\"热血\",\"1\"],[\"电竞\",\"715\"],[\"系统\",\"782\"],[\"异界\",\"692\"],[\"逆袭\",\"400\"],[\"天才\",\"783\"],[\"金手指\",\"756\"],[\"穿越\",\"373\"],[\"智商在线\",\"806\"],[\"言情\",\"745\"],[\"脑洞\",\"526\"]]},{\"name\":\"末世\",\"id\":\"556\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"科幻\",\"730\"],[\"爽文\",\"570\"],[\"言情\",\"745\"],[\"幻想言情\",\"774\"],[\"热血\",\"1\"],[\"重生\",\"779\"],[\"丧尸\",\"632\"],[\"杀伐果断\",\"202\"],[\"系统\",\"782\"],[\"异能\",\"644\"],[\"金手指\",\"756\"],[\"逆袭\",\"400\"],[\"穿越\",\"373\"],[\"都市\",\"758\"]]},{\"name\":\"萌宝\",\"id\":\"356\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"言情\",\"745\"],[\"现代言情\",\"772\"],[\"都市\",\"758\"],[\"豪门总裁\",\"682\"],[\"甜宠\",\"21\"],[\"优质网文\",\"1388\"],[\"HE\",\"581\"],[\"1V1\",\"585\"],[\"古代言情\",\"773\"],[\"古代\",\"604\"],[\"带球跑\",\"640\"],[\"穿越\",\"373\"],[\"追妻火葬场\",\"753\"],[\"爽文\",\"570\"]]},{\"name\":\"科幻\",\"id\":\"730\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"末世\",\"556\"],[\"爽文\",\"570\"],[\"未来世界\",\"704\"],[\"热血\",\"1\"],[\"杀伐果断\",\"202\"],[\"逆袭\",\"400\"],[\"系统\",\"782\"],[\"悬疑\",\"27\"],[\"异能\",\"644\"],[\"丧尸\",\"632\"],[\"重生\",\"779\"],[\"金手指\",\"756\"],[\"都市\",\"758\"],[\"脑洞\",\"526\"]]},{\"name\":\"民国\",\"id\":\"660\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"历史\",\"669\"],[\"近代\",\"728\"],[\"传记\",\"591\"],[\"现实题材\",\"12\"],[\"悬疑\",\"27\"],[\"奇闻异事\",\"769\"],[\"军事\",\"603\"],[\"人文社科\",\"796\"],[\"言情\",\"745\"],[\"探险\",\"697\"],[\"推理\",\"668\"],[\"现代言情\",\"772\"],[\"奇闻怪谈\",\"1396\"],[\"年代\",\"672\"]]},{\"name\":\"青春\",\"id\":\"761\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"现代言情\",\"772\"],[\"言情\",\"745\"],[\"校园\",\"691\"],[\"甜宠\",\"21\"],[\"都市\",\"758\"],[\"现实题材\",\"12\"],[\"1V1\",\"585\"],[\"短篇\",\"934\"],[\"治愈\",\"17\"],[\"HE\",\"581\"],[\"青梅竹马\",\"465\"],[\"虐恋\",\"16\"],[\"轻松\",\"3\"],[\"优质网文\",\"1388\"]]},{\"name\":\"军事\",\"id\":\"603\",\"tags\":[[\"全部\",\"-99\"],[\"有声小说\",\"923\"],[\"历史\",\"669\"],[\"近代\",\"728\"],[\"热血\",\"1\"],[\"人文社科\",\"796\"],[\"都市\",\"758\"],[\"民国\",\"660\"],[\"特种兵\",\"547\"],[\"社会\",\"723\"],[\"现实题材\",\"12\"],[\"特工\",\"188\"],[\"励志\",\"786\"],[\"红色文学\",\"535\"],[\"正剧\",\"707\"],[\"传记\",\"591\"]]}],\"drama\":[[\"推荐\",\"0\"],[\"虐恋\",\"16\"],[\"闪婚\",\"480\"],[\"真假千金\",\"812\"],[\"都市情感\",\"1273\"],[\"豪门总裁\",\"624\"],[\"复仇\",\"795\"],[\"追妻火葬场\",\"715\"],[\"女性成长\",\"1294\"],[\"穿越\",\"373\"],[\"甜宠\",\"21\"],[\"逆袭\",\"400\"],[\"重生\",\"784\"],[\"古装\",\"1272\"],[\"家庭\",\"670\"],[\"都市\",\"571\"],[\"打脸虐渣\",\"716\"],[\"悬疑\",\"27\"],[\"乡村\",\"590\"],[\"伦理\",\"1293\"],[\"婚姻\",\"718\"],[\"奇幻\",\"570\"],[\"玄幻仙侠\",\"1286\"],[\"战神\",\"527\"],[\"权谋\",\"790\"],[\"职场\",\"724\"],[\"年代\",\"572\"],[\"赘婿\",\"36\"],[\"马甲\",\"558\"],[\"强者回归\",\"402\"],[\"搞笑\",\"793\"],[\"小人物\",\"1296\"],[\"宅斗\",\"342\"],[\"系统\",\"787\"],[\"后悔流\",\"1202\"],[\"社会话题\",\"1290\"],[\"欢喜冤家\",\"464\"],[\"女强\",\"708\"],[\"扮猪吃虎\",\"799\"],[\"娱乐明星\",\"1295\"],[\"致富\",\"492\"],[\"民国\",\"573\"],[\"神医\",\"1269\"],[\"银发\",\"1297\"],[\"青春校园\",\"1288\"],[\"宫斗\",\"343\"],[\"商战\",\"723\"],[\"穿书\",\"338\"],[\"先婚后爱\",\"524\"],[\"异能\",\"727\"],[\"高手下山\",\"1299\"],[\"神豪\",\"37\"],[\"古代\",\"576\"],[\"破镜重圆\",\"467\"],[\"替身\",\"712\"],[\"团宠\",\"545\"],[\"志怪\",\"1383\"],[\"女帝\",\"617\"],[\"现代言情\",\"776\"],[\"脑洞\",\"526\"],[\"种田经商\",\"1291\"],[\"金手指\",\"726\"],[\"灵异\",\"1287\"],[\"玄幻\",\"566\"],[\"末世\",\"556\"],[\"武侠\",\"371\"],[\"偷听心声\",\"1258\"],[\"科幻\",\"594\"],[\"架空历史\",\"577\"],[\"暗恋\",\"719\"],[\"古代言情\",\"777\"],[\"大女主\",\"972\"],[\"修仙\",\"671\"],[\"奔跑吧推荐\",\"1300\"],[\"现实题材\",\"12\"],[\"女总裁\",\"89\"],[\"双重生\",\"828\"],[\"异界\",\"568\"],[\"鉴宝\",\"47\"]],\"animated\":[[\"推荐\",\"0\"],[\"古装\",\"1272\"],[\"穿越\",\"373\"],[\"逆袭\",\"400\"],[\"重生\",\"784\"],[\"玄幻仙侠\",\"1286\"],[\"都市\",\"571\"],[\"系统\",\"787\"],[\"打脸虐渣\",\"716\"],[\"末世\",\"556\"],[\"年代\",\"572\"],[\"金手指\",\"726\"],[\"悬疑\",\"27\"],[\"都市情感\",\"1273\"],[\"致富\",\"492\"],[\"奇幻\",\"570\"],[\"复仇\",\"795\"],[\"甜宠\",\"21\"],[\"异能\",\"727\"],[\"权谋\",\"790\"],[\"宅斗\",\"342\"],[\"乡村\",\"590\"],[\"玄幻\",\"566\"],[\"古代\",\"576\"],[\"穿书\",\"338\"],[\"豪门总裁\",\"624\"],[\"脑洞\",\"526\"],[\"修仙\",\"671\"],[\"囤物资\",\"894\"],[\"御兽\",\"423\"],[\"灵异\",\"1287\"],[\"种田\",\"317\"],[\"虐恋\",\"16\"],[\"家庭\",\"670\"],[\"爽文\",\"598\"],[\"随身空间\",\"345\"],[\"美食\",\"300\"],[\"追妻火葬场\",\"715\"],[\"小人物\",\"1296\"],[\"扮猪吃虎\",\"799\"],[\"丧尸\",\"732\"],[\"女帝\",\"617\"],[\"废柴\",\"796\"],[\"欢喜冤家\",\"464\"],[\"先婚后爱\",\"524\"],[\"民国\",\"573\"],[\"治愈\",\"17\"],[\"战神\",\"527\"],[\"替身\",\"712\"],[\"日常\",\"878\"],[\"网游\",\"734\"],[\"现代言情\",\"776\"],[\"通古今\",\"1203\"],[\"萌宠\",\"1249\"],[\"异界\",\"568\"],[\"恐怖\",\"599\"],[\"团宠\",\"545\"],[\"搞笑\",\"793\"],[\"烧脑\",\"600\"],[\"青春校园\",\"1288\"],[\"机甲\",\"733\"],[\"真假千金\",\"812\"],[\"失忆\",\"1251\"],[\"架空历史\",\"577\"],[\"后悔流\",\"1202\"],[\"都市脑洞\",\"883\"],[\"热血\",\"1\"],[\"娱乐明星\",\"1295\"],[\"古代言情\",\"777\"],[\"大女主\",\"972\"],[\"大明\",\"586\"],[\"社会话题\",\"1290\"],[\"马甲\",\"558\"],[\"双重生\",\"828\"],[\"偷听心声\",\"1258\"],[\"神医\",\"1269\"],[\"励志\",\"791\"],[\"破镜重圆\",\"467\"],[\"强者回归\",\"402\"],[\"娱乐圈\",\"357\"],[\"女扮男装\",\"706\"],[\"多女主\",\"945\"],[\"伦理\",\"1293\"],[\"女性成长\",\"1294\"],[\"职场\",\"724\"],[\"盗墓\",\"327\"],[\"公主\",\"113\"],[\"皇帝\",\"62\"],[\"鉴宝\",\"47\"],[\"神豪\",\"37\"],[\"赘婿\",\"36\"],[\"轻松\",\"3\"]]};\nfunction qmaNovelExplore(infoMap,source){return (function(){\nvar CHANNELS = ['男频', '女频', '出版'];\nvar OVER = [['全部状态', '-99'], ['完结', '1'], ['连载', '0']];\nvar WORDS = [['全部字数', '-99'], ['50万以下', '1'], ['50-100万', '2'], ['100-300万', '3'], ['300万以上', '4']];\nvar SORT = [['按热度', '0'], ['按更新', '1'], ['按评分', '2'], ['按新书', '3']];\nvar STYLE = {layout_flexGrow: 1, layout_flexBasisPercent: 1, layout_wrapBefore: true};\n\nvar state = qmSaveExploreState(\n    typeof infoMap === 'undefined' ? null : infoMap,\n    typeof source === 'undefined' ? null : source\n);\nvar action = \"(function(){var currentInfo=typeof infoMap==='undefined'?null:infoMap;var currentSource=typeof source==='undefined'?null:source;qmSaveExploreState(currentInfo,currentSource);try{java.reUiView(false);}catch(e){try{java.refreshExplore();}catch(e2){}}})()\";\nvar encoded = encodeURIComponent(JSON.stringify(state));\n\nfunction labels(options) {\n    return options.map(function (option) { return option[0]; });\n}\nfunction selectedLabel(options, value) {\n    for (var i = 0; i < options.length; i++) {\n        if (String(options[i][1]) == String(value)) return options[i][0];\n    }\n    return options[0][0];\n}\n\nvar resultName = '查看筛选结果 · ' + state.channel + ' · ' + state.group + ' · ' + state.tag;\nvar resultUrl = '<js>qmBuildExploreUrlFromEncoded(java,' + JSON.stringify(encoded) + ',typeof page==\"undefined\"?1:page)<\/js>';\nreturn JSON.stringify([\n    {title: '频道', type: 'select', chars: CHANNELS, default: state.channel, action: action, style: STYLE},\n    {title: '分类分组', type: 'select', chars: qmExploreGroupNames(state.channel), default: state.group, action: action, style: STYLE},\n    {title: '分类标签', type: 'select', chars: qmExploreTagNames(state.channel, state.group), default: state.tag, action: action, style: STYLE},\n    {title: '状态', type: 'select', chars: labels(OVER), default: selectedLabel(OVER, state.over), action: action, style: STYLE},\n    {title: '字数', type: 'select', chars: labels(WORDS), default: selectedLabel(WORDS, state.words), action: action, style: STYLE},\n    {title: '排序', type: 'select', chars: labels(SORT), default: selectedLabel(SORT, state.sort), action: action, style: STYLE},\n    {title: resultName, type: 'url', url: resultUrl, style: STYLE}\n]);\n})()\n;}\nvar QMA_ORIGINAL_LOGIN = [{\"name\": \"开启段评（刷新正文后生效）\", \"type\": \"button\", \"action\": \"qmpcSetEnabled.call(this,true)\"}, {\"name\": \"关闭段评（刷新正文后生效）\", \"type\": \"button\", \"action\": \"qmpcSetEnabled.call(this,false)\"}, {\"name\": \"查看最近段评加载状态\", \"type\": \"button\", \"action\": \"qmpcShowStatus.call(this)\"}];\n\/* Independent discovery states; catalogs are snapshots of verified official metadata. *\/\nvar QMA_DISC = (function(){\n  var MODES=[['小说','novel'],['听书','audio'],['漫剧','animated'],['短剧','drama']];\n  var OVER=[['全部','-99'],['完结','1'],['连载','0']];\n  var SORT=[['最高热度','1'],['最近更新','2'],['最新上架','3']];\n  var KEYS={category:'听书分类',tag:'听书标签',over:'听书状态',sort:'听书排序'};\n  var STYLE={layout_flexGrow:1,layout_flexBasisPercent:1,layout_wrapBefore:true};\n  function src(ctx){return QMA_NET.source(ctx);}\n  function j(ctx){return QMA_NET.java(ctx);}\n  function get(map,key){return qmExploreMapGet(map,key);}\n  function put(map,key,value){qmExploreMapPut(map,key,value);}\n  function stored(ctx,key){try{return JSON.parse(String(src(ctx).get(key)||'{}'));}catch(e){return {};}}\n  function option(options,value,fallback){\n    for(var i=0;i<options.length;i++)if(String(value)===options[i][0]||String(value)===options[i][1])return String(options[i][1]);\n    return String(fallback);\n  }\n  function label(options,value){for(var i=0;i<options.length;i++)if(String(options[i][1])===String(value))return options[i][0];return options[0][0];}\n  function mode(){return option(MODES,src(this).get('qma_explore_mode'),'novel');}\n  function loginUi(){\n    var rows=[{name:'发现内容类型',type:'select',chars:MODES.map(function(x){return x[0];}),default:label(MODES,mode.call(this)),\n      action:'QMA_DISC.setMode.call(this,result)',style:STYLE}];\n    rows=rows.concat(QMA_ORIGINAL_LOGIN);\n    rows.push({name:'聚合使用说明',type:'button',action:'QMA.help.call(this)'},\n      {name:'查看媒体接口状态',type:'button',action:'QMA.status.call(this)'},\n      {name:'测试媒体游客登录',type:'button',action:'QMA.login.call(this)'},\n      {name:'重置媒体游客身份',type:'button',action:'QMA.reset.call(this)'});\n    return JSON.stringify(rows);\n  }\n  function setMode(values){\n    var selected=typeof values==='string'?values:get(values,'发现内容类型');\n    var chosen=option(MODES,selected,'');\n    if(!chosen)throw new Error('请选择小说、听书、漫剧或短剧');\n    var s=src(this), previous=mode.call(this);\n    s.put('qma_explore_mode',chosen);\n    \/\/ Merge only our key; preserve other saved login settings.\n    try {\n      var saved=JSON.parse(String(s.getLoginInfo()||'{}'));\n      saved['发现内容类型']=label(MODES,chosen);\n      s.putLoginInfo(JSON.stringify(saved));\n    }catch(ignored){}\n    var cleared=false;\n    if(previous!==chosen){try{s.refreshExplore();cleared=true;}catch(ignored2){}}\n    j(this).longToast('发现已切换为'+label(MODES,chosen)+(cleared?'，缓存已清除':'')+'。返回发现后收起再展开书源；如仍显示旧布局，请长按书源→刷新。');\n  }\n  function audioCategory(value){\n    for(var i=0;i<QMA_CATALOG.audio.length;i++){\n      var c=QMA_CATALOG.audio[i];if(String(value)===c.name||String(value)===c.id)return c;\n    }\n    return QMA_CATALOG.audio[0];\n  }\n  function audioNormalize(value){\n    value=value||{};var c=audioCategory(value.category);\n    return {category:c.id,tag:option(c.tags,value.tag,'-99'),over:option(OVER,value.over,'-99'),sort:option(SORT,value.sort,'1')};\n  }\n  function audioState(ctx,info){\n    var old=audioNormalize(stored(ctx,'qma_audio_filters'));\n    var category=audioCategory(get(info,KEYS.category)||old.category);\n    var tag=category.id!==old.category?'-99':get(info,KEYS.tag)||old.tag;\n    var value=audioNormalize({category:category.id,tag:tag,over:get(info,KEYS.over)||old.over,sort:get(info,KEYS.sort)||old.sort});\n    Object.keys(KEYS).forEach(function(k){\n      var display=k==='category'?category.name:k==='tag'?label(category.tags,value.tag):label(k==='over'?OVER:SORT,value[k]);\n      put(info,KEYS[k],display);\n    });\n    try{if(info&&info.saveNow)info.saveNow();}catch(ignored){}\n    src(ctx).put('qma_audio_filters',JSON.stringify(value));\n    return value;\n  }\n  function audioChanged(info){\n    audioState(this,info);\n    try{src(this).refreshExplore();}catch(ignored){}\n    \/\/ From an ExploreKind action this callback rebuilds discovery, not login.\n    try{j(this).refreshExplore();}catch(ignored2){}\n  }\n  function explore(info){\n    var selected=mode.call(this);\n    if(selected==='novel')return qmaNovelExplore.call(this,info,src(this));\n    if(selected!=='audio'){\n      return JSON.stringify(QMA_CATALOG[selected].map(function(t){\n        return {title:t[0],type:'url',url:'@js:QMA.exploreUrl.call(this,'+JSON.stringify(selected)+',page,'+JSON.stringify(t[1])+')',\n          style:{layout_flexBasisPercent:t[0].length>4?0.5:0.25,layout_flexGrow:1}};\n      }));\n    }\n    var value=audioState(this,info),category=audioCategory(value.category);\n    var action='QMA_DISC.audioChanged.call(this,typeof infoMap===\"undefined\"?null:infoMap)';\n    var values=[QMA_CATALOG.audio.map(function(c){return c.name;}),category.tags.map(function(t){return t[0];}),OVER.map(function(t){return t[0];}),SORT.map(function(t){return t[0];})];\n    var defaults=[category.name,label(category.tags,value.tag),label(OVER,value.over),label(SORT,value.sort)];\n    var keys=[KEYS.category,KEYS.tag,KEYS.over,KEYS.sort],names=['分类','标签','状态','排序'];\n    \/\/ beta.45 ExploreKind.equals ignores chars. Change the tag row's action too,\n    \/\/ so Compose receives new options even when both defaults are still 全部.\n    var rows=keys.map(function(k,index){return {title:k,viewName:JSON.stringify(names[index]),type:'select',chars:values[index],default:defaults[index],\n      action:action+(index===1?'; \/* category '+category.id+' *\/':''),style:STYLE};});\n    var name='查看筛选结果 · '+defaults.join(' · ');\n    rows.push({title:name,type:'url',url:'@js:QMA.exploreUrl.call(this,\"audio\",page,'+JSON.stringify(value)+')',style:STYLE});\n    return JSON.stringify(rows);\n  }\n  function audioParams(value,page){\n    value=audioNormalize(value);\n    var p={category_id:value.category,category_type:'2',page:String(page),need_filters:'1',need_category:'1',over:value.over,sort:value.sort};\n    if(value.tag!=='-99')p.tag_ids=value.tag;\n    return p;\n  }\n  function mem(ctx,key,value){\n    var c=QMA_NET.cache(ctx);\n    try{if(value!==undefined){if(c)c.putMemory(key,JSON.stringify(value));return value;}return JSON.parse(String(c&&c.getFromMemory(key)||'{}'));}catch(e){return {};}\n  }\n  function audioKey(value){return 'qma-audio-pages-'+qmMd5(JSON.stringify(audioNormalize(value)));}\n  function audioEnded(ctx,value,page){if(page===1)mem(ctx,audioKey(value),{});return Number(mem(ctx,audioKey(value)).total)>0&&page>Number(mem(ctx,audioKey(value)).total);}\n  function audioPages(ctx,value,metadata){if(metadata&&\/^\\d+$\/.test(String(metadata.total_pages)))mem(ctx,audioKey(value),{total:Number(metadata.total_pages)});}\n  function videoTag(selected,tag){\n    var tags=QMA_CATALOG[selected]||[];\n    for(var i=0;i<tags.length;i++)if(String(tag)===tags[i][1])return String(tag);\n    throw new Error('视频分类已失效，请刷新发现页');\n  }\n  function videoKey(m){return 'qma-video-pages-'+m.mode+'-'+videoTag(m.mode,m.tag||'0');}\n  function videoParams(ctx,m){\n    var key=videoKey(m),state=m.page===1?{pages:{}}:mem(ctx,key);\n    if(m.page===1)mem(ctx,key,state);\n    if(state.end&&m.page>state.end)return null;\n    var ids=[],seen={};\n    Object.keys(state.pages||{}).sort(function(a,b){return Number(a)-Number(b);}).forEach(function(p){if(Number(p)<m.page)ids=ids.concat(state.pages[p]);});\n    ids=ids.filter(function(x){if(seen[x])return false;seen[x]=1;return true;}).slice(-120);\n    var upload=ids.map(function(x){return {id:x,item_type:m.mode==='animated'?'14':'11',ts:String(Date.now()),type:'0'};});\n    var p={page:String(m.page),playlet_privacy:'1',read_preference:'0',refresh_state:m.page===1?'4':'7',tag_id:m.tag||'0',upload_ids:JSON.stringify(upload)};\n    if(m.mode==='animated')p.change_tag=m.page===1?'1':'0';\n    return p;\n  }\n  function videoRows(ctx,m,rows,next){\n    var key=videoKey(m),state=mem(ctx,key),prior={},local={};state.pages=state.pages||{};\n    Object.keys(state.pages).forEach(function(p){if(Number(p)<m.page)state.pages[p].forEach(function(x){prior[x]=true;});});\n    var filtered=rows.filter(function(x){var i=String(x.playlet_id||x.id||'');if(!i||prior[i]||local[i])return false;local[i]=true;return true;});\n    state.pages[String(m.page)]=Object.keys(local);\n    if(!rows.length||next!=null&&(String(next)===''||Number(next)<=m.page))state.end=m.page;\n    Object.keys(state.pages).forEach(function(p){if(Number(p)<m.page-8)delete state.pages[p];});\n    mem(ctx,key,state);return filtered;\n  }\n  return {mode:mode,loginUi:loginUi,setMode:setMode,explore:explore,audioNormalize:audioNormalize,audioChanged:audioChanged,\n    audioParams:audioParams,audioEnded:audioEnded,audioPages:audioPages,videoTag:videoTag,videoParams:videoParams,videoRows:videoRows};\n})();\n","lastUpdateTime":"1791476657532","respondTime":180000,"ruleBookInfo":{"author":"author","coverUrl":"image_link","init":"@js:\n(function(){\nif (QMA.meta.call(this,baseUrl,result)) return JSON.stringify(QMA.info.call(this,baseUrl,result));\nreturn (function(){\nreturn (function () {\n    var raw = String(result == null ? '' : result);\n    var payload;\n    try {\n        payload = JSON.parse(raw);\n    } catch (e) {\n        throw new Error('七猫详情接口未返回有效 JSON，请查看调试响应');\n    }\n    function isBook(value) {\n        return value && typeof value === 'object' && !Array.isArray(value)\n            && value.id != null && String(value.id) !== ''\n            && typeof value.title === 'string' && value.title.replace(\/\\s\/g, '') !== ''\n            && !('books' in value) && !('filters' in value);\n    }\n    function isFlatBook(value) {\n        return isBook(value) && ('author' in value || 'words_num' in value || 'image_link' in value);\n    }\n    if (payload && payload.data && isBook(payload.data.book)) {\n        return JSON.stringify(payload.data.book);\n    }\n    if (payload && isFlatBook(payload.data)) {\n        return JSON.stringify(payload.data);\n    }\n    if (isFlatBook(payload)) return JSON.stringify(payload);\n    throw new Error('七猫响应中没有有效书籍详情；分类页或错误响应不能当作书籍解析');\n})();\n}).call(this);\n}).call(this)","intro":"&nbsp;&nbsp;🔖更新时间：{{java.timeFormat(java.getString('$.update_time')*1000)}}\t{{'\\n&lrm;\\n'}}🏷️ 标签：{{String(java.getString('$.book_tag_list[*].title&&$.category_over_words##・|\\\\s##，')).split('，').filter(function(v,i,a){return v && a.indexOf(v)==i;}).join('，')}}{{'\\n'+'​'}}\n\n{{$.intro}}\n{{'\\n&lrm;\\n'}}📍版权来源：{{$.statement##如有疑问可联系:fawu@qimao.com或客服QQ:3598987377}}\n<js>QMA.meta.call(this,baseUrl,result) ? java.getString(\"$.intro\") : result<\/js>","kind":"book_tag_list[*].title##\\s##，","lastChapter":"{{$.latest_chapter_title}} • {{java.timeFormat(java.getString('$.update_time')*1000)}}\n<js>result.replace(\/\\s\\d+:\\d+\/,'').replace(\/\\\/\/g,'-')<\/js>\n<js>QMA.meta.call(this,baseUrl,result) ? java.getString(\"$.latest_chapter_title\") : result<\/js>","name":"title@put:{\"bid\":\"$.id\"}","tocUrl":"@js:QMA.meta.call(this,baseUrl,result) ? java.getString(\"$.toc_url\") : qmToc(java.getString(\"$.id\"))","wordCount":"words_num"},"ruleContent":{"content":"@js:\nif (QMA.meta.call(this,baseUrl,result)) { QMA.content.call(this,baseUrl,result); } else {\nvar seg = String(java.hexDecodeToString(result)).split('\/');\nvar bid = String(java.get('bid'));\nvar story = seg[2] == '1';\nvar out, valid = false, res;\ntry {\n    res = JSON.parse(java.ajax(qmContent(bid, seg[0], story)));\n    if (!(res.data && res.data.content)) res = JSON.parse(java.ajax(qmContent(bid, seg[0], !story)));\n    if (res.data && res.data.content) {\n        out = qmDecode(res.data.content);\n        valid = true;\n    } else out = '七猫服务器未返回本章正文：' + (res.errors ? res.errors.details : '未知错误');\n} catch (e) { out = '正文获取失败：' + e; }\nif (valid) out = qmpcAnnotate.call(this, out, bid, seg[0], String(res.data.content_md5 || seg[1] || ''));\nout;\n\n}"},"ruleExplore":{"bookList":"@js:QMA.list.call(this,result,baseUrl)","name":"title","author":"author","bookUrl":"url","coverUrl":"cover","intro":"intro","kind":"kind","lastChapter":"last","wordCount":"words"},"ruleSearch":{"bookList":"@js:QMA.list.call(this,result,baseUrl)","name":"title","author":"author","bookUrl":"url","coverUrl":"cover","intro":"intro","kind":"kind","lastChapter":"last","wordCount":"words"},"ruleToc":{"chapterList":"@js:\nQMA.meta.call(this,baseUrl,result) ? QMA.toc.call(this,baseUrl,result) : (function(){var d=JSON.parse(String(result)).data; java.put(\"qm_agent\",String(d.reader_type)==\"4\"?\"1\":\"0\"); return d.chapter_lists;}).call(this)","chapterName":"title","chapterUrl":"@js:QMA.meta.call(this,baseUrl,result) ? java.getString(\"$.url\") : qmChapterUrl(java.getString(\"$.id\"),java.getString(\"$.content_md5\"),java.get(\"qm_agent\"))","updateTime":"@js:QMA.meta.call(this,baseUrl,result) ? java.getString(\"$.info\") : java.getString(\"$.words\")+\"字\""},"searchUrl":"@js:QMA.searchUrl.call(this,key,page)","weight":0,"bookUrlPattern":"https:\/\/api-bc\\.wtzw\\.com\/api\/v4\/book\/detail\\?.*","loginUrl":"function login() {}","loginUi":"@js:QMA_DISC.loginUi.call(this)"}]