|
| 1 | +/* |
| 2 | +@header({ |
| 3 | + searchable: 1, |
| 4 | + filterable: 1, |
| 5 | + quickSearch: 0, |
| 6 | + title: '七猫短剧', |
| 7 | + '类型': '影视', |
| 8 | + lang: 'ds' |
| 9 | +}) |
| 10 | +*/ |
| 11 | + |
| 12 | +const h_ost = 'https://api-store.qmplaylet.com'; |
| 13 | +const h_ost1 = 'https://api-read.qmplaylet.com'; |
| 14 | +const keys = 'd3dGiJc651gSQ8w1'; |
| 15 | +const char_map = { |
| 16 | + '+': 'P', '/': 'X', '0': 'M', '1': 'U', '2': 'l', '3': 'E', '4': 'r', '5': 'Y', '6': 'W', '7': 'b', '8': 'd', '9': 'J', |
| 17 | + 'A': '9', 'B': 's', 'C': 'a', 'D': 'I', 'E': '0', 'F': 'o', 'G': 'y', 'H': '_', 'I': 'H', 'J': 'G', 'K': 'i', 'L': 't', |
| 18 | + 'M': 'g', 'N': 'N', 'O': 'A', 'P': '8', 'Q': 'F', 'R': 'k', 'S': '3', 'T': 'h', 'U': 'f', 'V': 'R', 'W': 'q', 'X': 'C', |
| 19 | + 'Y': '4', 'Z': 'p', 'a': 'm', 'b': 'B', 'c': 'O', 'd': 'u', 'e': 'c', 'f': '6', 'g': 'K', 'h': 'x', 'i': '5', 'j': 'T', |
| 20 | + 'k': '-', 'l': '2', 'm': 'z', 'n': 'S', 'o': 'Z', 'p': '1', 'q': 'V', 'r': 'v', 's': 'j', 't': 'Q', 'u': '7', 'v': 'D', |
| 21 | + 'w': 'w', 'x': 'n', 'y': 'L', 'z': 'e' |
| 22 | +}; |
| 23 | + |
| 24 | +// 公共函数:获取请求头配置(减少重复代码) |
| 25 | +async function getCommonRequestOptions(ruleHeaders) { |
| 26 | + let formDatas = await getHeaderX(); |
| 27 | + // 合并公共请求头与规则中的请求头 |
| 28 | + let headers = { ...formDatas, ...ruleHeaders }; |
| 29 | + return { method: 'GET', headers }; |
| 30 | +} |
| 31 | + |
| 32 | +async function getQmParamsAndSign() { |
| 33 | + try { |
| 34 | + let sessionId = Math.floor(Date.now()).toString(); // 确保无小数点,纯整数字符串 |
| 35 | + let data = { |
| 36 | + "static_score": "0.8", "uuid": "00000000-7fc7-08dc-0000-000000000000", |
| 37 | + "device-id": "20250220125449b9b8cac84c2dd3d035c9052a2572f7dd0122edde3cc42a70", |
| 38 | + "mac": "", "sourceuid": "aa7de295aad621a6", "refresh-type": "0", "model": "22021211RC", |
| 39 | + "wlb-imei": "", "client-id": "aa7de295aad621a6", "brand": "Redmi", "oaid": "", |
| 40 | + "oaid-no-cache": "", "sys-ver": "12", "trusted-id": "", "phone-level": "H", |
| 41 | + "imei": "", "wlb-uid": "aa7de295aad621a6", "session-id": sessionId // 动态参数对齐 |
| 42 | + }; |
| 43 | + let jsonStr = JSON.stringify(data, null, 0); // 禁用缩进,确保无空格 |
| 44 | + console.log('[调试] JSON序列化结果:', jsonStr); |
| 45 | + // 2.3 Base64编码:对齐Python base64.b64encode(处理UTF-8字符) |
| 46 | + let utf8Encoded = encodeURIComponent(jsonStr); // 转UTF-8编码 |
| 47 | + let base64Str = btoa(unescape(utf8Encoded)); // 对齐Python b64encode |
| 48 | + console.log('[调试] Base64编码结果:', base64Str); // 调试:对比Python的encoded |
| 49 | + // 2.4 字符映射:逐字符替换,确保无遗漏 |
| 50 | + let qmParams = ''; |
| 51 | + for (let c of base64Str) { |
| 52 | + qmParams += char_map[c] || c; |
| 53 | + } |
| 54 | + console.log('[调试] qm-params结果:', qmParams); |
| 55 | + // 2.5 签名参数拼接: |
| 56 | + let paramsStr = `AUTHORIZATION=app-version=10001application-id=com.duoduo.readchannel=unknownis-white=net-env=5platform=androidqm-params=${qmParams}reg=${keys}`; |
| 57 | + console.log('[调试] 签名原始字符串:', paramsStr); |
| 58 | + // 2.6 计算MD5签名 |
| 59 | + let sign = await md5(paramsStr); |
| 60 | + console.log('[调试] 生成的sign:', sign); |
| 61 | + return { qmParams, sign }; |
| 62 | + } catch (e) { |
| 63 | + console.error('[错误] qm-params/sign生成失败:', e); |
| 64 | + throw e; // 抛出错误,避免传递无效参数 |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +async function getHeaderX() { |
| 69 | + let { qmParams, sign } = await getQmParamsAndSign(); |
| 70 | + return { |
| 71 | + 'net-env': '5', 'reg': '', 'channel': 'unknown', 'is-white': '', 'platform': 'android', |
| 72 | + 'application-id': 'com.duoduo.read', 'authorization': '', 'app-version': '10001', |
| 73 | + 'user-agent': 'webviewversion/0', 'qm-params': qmParams, // 确保键名正确 |
| 74 | + 'sign': sign // 确保键名正确 |
| 75 | + }; |
| 76 | +} |
| 77 | + |
| 78 | +var rule = { |
| 79 | + title: '七猫短剧', |
| 80 | + host: h_ost, |
| 81 | + homeUrl: '/api/v1/playlet/index?tag_id=0&playlet_privacy=1&operation=1', |
| 82 | + searchUrl: '/api/v1/playlet/search?keyword=fykey&page=fypage', |
| 83 | + url: '/api/v1/playlet/index?tag_id=fyclass&page=fypage', |
| 84 | + headers: { |
| 85 | + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36' |
| 86 | + }, |
| 87 | + timeout: 5000, |
| 88 | + filterable: 1, |
| 89 | + limit: 90, |
| 90 | + multi: 1, |
| 91 | + searchable: 1, |
| 92 | + play_parse: true, |
| 93 | + search_match: true, |
| 94 | + |
| 95 | + class_parse: async function() { |
| 96 | + let signString = `operation=1playlet_privacy=1tag_id=0${keys}`; |
| 97 | + let apiSign = md5(signString); |
| 98 | + let url = `${h_ost}/api/v1/playlet/index?tag_id=0&playlet_privacy=1&operation=1&sign=${apiSign}`; |
| 99 | + |
| 100 | + // 调用公共函数获取请求配置 |
| 101 | + let options = await getCommonRequestOptions(rule.headers); |
| 102 | + let html = await request(url, options); |
| 103 | + let data = JSON.parse(html); |
| 104 | + |
| 105 | + let classList = []; |
| 106 | + let duoxuan = ['0', '1', '2', '3', '4']; // 分类筛选维度 |
| 107 | + for (let duo of duoxuan) { |
| 108 | + let tags = data?.data?.tag_categories?.[Number(duo)]?.tags || []; |
| 109 | + for (let vod of tags) { |
| 110 | + classList.push({ |
| 111 | + type_id: String(vod.tag_id), |
| 112 | + type_name: vod.tag_name || '' |
| 113 | + }); |
| 114 | + } |
| 115 | + } |
| 116 | + return { class: classList, filters: {} }; |
| 117 | + }, |
| 118 | + |
| 119 | + 一级: async function() { |
| 120 | + let { MY_CATE, MY_PAGE } = this; |
| 121 | + let cid = MY_CATE || 0; |
| 122 | + let page = MY_PAGE || 1; |
| 123 | + let signString; |
| 124 | + |
| 125 | + if (page === 1) { |
| 126 | + signString = `operation=1playlet_privacy=1tag_id=${cid}${keys}`; |
| 127 | + } else { |
| 128 | + signString = `next_id=${page}operation=1playlet_privacy=1tag_id=${cid}${keys}`; |
| 129 | + } |
| 130 | + let sign = md5(signString); |
| 131 | + console.log('[调试] 一级列表sign:', sign); |
| 132 | + |
| 133 | + // 构造分页URL |
| 134 | + let url = page === 1 ? |
| 135 | + `${h_ost}/api/v1/playlet/index?tag_id=${cid}&playlet_privacy=1&operation=1&sign=${sign}` : |
| 136 | + `${h_ost}/api/v1/playlet/index?tag_id=${cid}&next_id=${page}&playlet_privacy=1&operation=1&sign=${sign}`; |
| 137 | + |
| 138 | + // 调用公共函数获取请求配置 |
| 139 | + let options = await getCommonRequestOptions(rule.headers); |
| 140 | + let html = await request(url, options); |
| 141 | + let data = JSON.parse(html); |
| 142 | + let videoList = data?.data?.list || []; |
| 143 | + |
| 144 | + let videos = videoList.map(vod => ({ |
| 145 | + title: vod.title || '未知标题', |
| 146 | + img: vod.image_link || '', |
| 147 | + desc: `${vod.tags} ${vod.total_episode_num}集 ${vod.hot_value}`, |
| 148 | + url: encodeURIComponent(vod.playlet_id) |
| 149 | + })); |
| 150 | + return setResult(videos); |
| 151 | + }, |
| 152 | + |
| 153 | + 二级: async function() { |
| 154 | + let { orId } = this; |
| 155 | + let did = decodeURIComponent(orId); |
| 156 | + let signString = `playlet_id=${did}${keys}`; |
| 157 | + let sign = md5(signString); |
| 158 | + console.log('[调试] 详情接口sign:', sign); |
| 159 | + |
| 160 | + let detailUrl = `${h_ost1}/player/api/v1/playlet/info?playlet_id=${did}&sign=${sign}`; |
| 161 | + // 调用公共函数获取请求配置 |
| 162 | + let options = await getCommonRequestOptions(rule.headers); |
| 163 | + let html = await request(detailUrl, options); |
| 164 | + |
| 165 | + let data = JSON.parse(html).data; |
| 166 | + // 构造播放地址列表:排序$地址拼接 |
| 167 | + let play_url = data.play_list.map(it => `${it.sort}$${it.video_url}`).join('#'); |
| 168 | + return { |
| 169 | + vod_name: data.title || "未知标题", |
| 170 | + vod_pic: data.image_link || "未知图片", |
| 171 | + vod_actor: "", |
| 172 | + vod_remarks: `${data.tags} ${data.total_episode_num}集`, |
| 173 | + vod_content: `${data.intro}` || "未知剧情", |
| 174 | + vod_play_from: '七猫短剧', |
| 175 | + vod_play_url: play_url |
| 176 | + }; |
| 177 | + }, |
| 178 | + |
| 179 | + 搜索: async function() { |
| 180 | + let { KEY, MY_PAGE } = this; |
| 181 | + let page = MY_PAGE || 1; |
| 182 | + let d = []; |
| 183 | + let trackId = 'ec1280db127955061754851657967'; |
| 184 | + |
| 185 | + let signString = `extend=page=${page}read_preference=0track_id=${trackId}wd=${KEY}${keys}`; |
| 186 | + let sign = md5(signString); |
| 187 | + console.log('[调试] 搜索接口sign:', sign); |
| 188 | + let encodedKey = encodeURIComponent(KEY); |
| 189 | + let url = `${h_ost}/api/v1/playlet/search?extend=&page=${page}&wd=${encodedKey}&read_preference=0&track_id=${trackId}&sign=${sign}`; |
| 190 | + |
| 191 | + // 调用公共函数获取请求配置 |
| 192 | + let options = await getCommonRequestOptions(rule.headers); |
| 193 | + let html = await request(url, options); |
| 194 | + |
| 195 | + let data = JSON.parse(html); |
| 196 | + let videoList = data?.data?.list || []; |
| 197 | + videoList.forEach((vod) => { |
| 198 | + let cleanTitle = (vod.title || '').replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); |
| 199 | + |
| 200 | + if (rule.search_match && !new RegExp(KEY, "i").test(cleanTitle)) { |
| 201 | + console.log(`[调试] 排除不符合搜索关键词的名称: ${cleanTitle}`); |
| 202 | + return; |
| 203 | + } |
| 204 | + |
| 205 | + d.push({ |
| 206 | + title: cleanTitle, |
| 207 | + img: vod.image_link || '', |
| 208 | + desc: `${vod.total_num}`, |
| 209 | + url: encodeURIComponent(vod.id) |
| 210 | + }); |
| 211 | + }); |
| 212 | + |
| 213 | + return setResult(d); |
| 214 | + }, |
| 215 | + |
| 216 | + lazy: async function() { |
| 217 | + let { input } = this; |
| 218 | + console.log('[调试] 播放地址:', input); |
| 219 | + return { parse: 0, url: input }; |
| 220 | + } |
| 221 | +}; |
0 commit comments