diff --git a/js/可可影视.js b/js/可可影视.js new file mode 100644 index 0000000..1932c86 --- /dev/null +++ b/js/可可影视.js @@ -0,0 +1,105 @@ +/* +@header({ + searchable: 2, + filterable: 1, + quickSearch: 0, + title: '可可影视[优]', + '类型': '影视', + lang: 'dr2' +}) +*/ + +var rule = { + title: '可可影视[优]', + host: 'https://www.keke5.app', + //host: 'https://www.kkys01.com', + url: '/show/fyclass-----2-fypage.html', + //url: '/show/fyclass-fyfilter-fypage.html', + searchUrl: '/search?t=/fVll50WsMh42iBP633zlA==&k=**&page=fypage', + searchable: 2, + quickSearch: 0, + filterable: 1, + headers: { + 'User-Agent': 'MOBILE_UA', + }, + class_parse: '#nav-swiper&&.nav-swiper-slide;a&&Text;a&&href;/(\\w+).html', + cate_exclude: 'Netflix|今日更新|专题列表|排行榜', + tab_exclude:'可可影视提供', + tab_order: ['超清', '蓝光', '极速蓝光'], + tab_remove:['4K(高峰不卡)'], + play_parse: true, + lazy: $js.toString(() => { + input = { + parse: 1, + url: input, + js: 'document.querySelector("#my-video video").click()', + } + }), + limit: 20, + 推荐: '.section-box:eq(2)&&.module-box-inner&&.module-item;*;*;*;*', + double: false, + 一级: '.module-box-inner&&.module-item;.v-item-title:eq(1)&&Text;img:last-of-type&&data-original;.v-item-bottom&&span&&Text;a&&href', + 二级: { + title: '.detail-pic&&img&&alt;.detail-tags&&a&&Text', + img: '.detail-pic&&img&&data-original', + desc: '.detail-info-row-main:eq(-2)&&Text;.detail-tags&&a&&Text;.detail-tags&&a:eq(1)&&Text;.detail-info-row-main:eq(1)&&Text;.detail-info-row-main&&Text', + content: '.detail-desc&&Text', + tabs: '.source-item-label', + //tabs: 'body&&.source-item-label[id]', + lists: '.episode-list:eq(#id) a', + }, + 搜索: '.search-result-list&&a;.title:eq(1)&&Text;.search-result-item-pic&&img&&data-original;.search-result-item-header&&Text;a&&href;.desc&&Text', + 图片替换:'https://www.keke5.app=>https://vres.zyxpedu.com', + 预处理: $js.toString(() => { + function extractHashFromResponse(ruleHost) { + try { + const response = request(ruleHost); + const regex = /a0_0x2a54\s*=\s*\['([^']+)'/; + const match = response.match(regex); + return match ? match[1] : ''; + } catch (error) { + console.error('请求失败:', error); + return ''; + } + } + + function sha1ToUint8ArrayLatin1(input) { + const hash = CryptoJS.SHA1(input); + const latin1String = hash.toString(CryptoJS.enc.Latin1); + const uint8Array = new Uint8Array(latin1String.length); + + for (let i = 0; i < latin1String.length; i++) { + uint8Array[i] = latin1String.charCodeAt(i); + } + + return uint8Array; + } + + function run(c, n1) { + let i = 0; + while (i < 1000000) { + const input = c + i; + const hash = sha1ToUint8ArrayLatin1(input); + if (hash[n1] === 0xb0 && hash[n1 + 1] === 0x0b) { + const myck = 'cdndefend_js_cookie=' + c + i; + console.log('找到 myck:', myck); + rule.headers['cookie'] = myck; + setItem('mycookie', myck); + break; + } + i++; + } + console.log('未找到符合条件的 i'); + } + + let hash = extractHashFromResponse(rule.host); + if (hash != '' && hash != getItem('myhash')) { + setItem('mycookie', ''); + setItem('myhash', hash); + run(hash, parseInt('0x' + hash[0], 16)); + } + if (getItem('mycookie')) { + rule.headers['cookie'] = getItem('mycookie'); + } + }), +} \ No newline at end of file diff --git a/js/咖啡体育.js b/js/咖啡体育.js new file mode 100644 index 0000000..17ec9b9 --- /dev/null +++ b/js/咖啡体育.js @@ -0,0 +1,1119 @@ +// @name 咖啡直播 +// @author modified by ChatGPT +// @description 咖啡直播体育赛事直播 + 录像回放 +// @version 1.3.0 + +//const host = 'https://kafeizhibo.com'; +const host = 'https://kafeizhibo.cc'; +const headers = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + 'Accept': 'application/json, text/plain, */*', + 'Referer': host + '/pc/live', + 'Origin': host +}; + +// ========== 工具函数 ========== + +function fixUrl(path) { + if (!path) return ''; + const value = String(path).trim(); + if (!value) return ''; + if (value.startsWith('http')) return value; + if (value.startsWith('//')) return 'https:' + value; + return host + (value.startsWith('/') ? '' : '/') + value; +} + +function safeText(value, fallback = '') { + if (value === undefined || value === null) return fallback; + return String(value).trim(); +} + +function safeNumber(value, fallback = '') { + if (value === undefined || value === null || value === '') return fallback; + return String(value); +} + +function cleanName(name) { + return safeText(name, '播放').replace(/[$#]/g, ' ').trim() || '播放'; +} + +function cleanPlayUrl(url) { + return safeText(url).replace(/#/g, '%23'); +} + +function buildQuery(params = {}) { + const arr = []; + for (const [key, value] of Object.entries(params)) { + if (value === undefined || value === null || value === '') continue; + arr.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + } + return arr.join('&'); +} + +function getContent(res) { + if (!res) return ''; + if (typeof res === 'string') return res; + if (typeof res.content === 'string') return res.content; + if (typeof res.body === 'string') return res.body; + if (typeof res.data === 'string') return res.data; + if (typeof res.content === 'object') return JSON.stringify(res.content); + if (typeof res.body === 'object') return JSON.stringify(res.body); + if (typeof res.data === 'object') return JSON.stringify(res.data); + return ''; +} + +async function requestText(url, params = {}, extraHeaders = {}) { + const query = buildQuery(params); + const finalUrl = query ? `${url}${url.includes('?') ? '&' : '?'}${query}` : url; + const r = await req(finalUrl, { + headers: Object.assign({}, headers, extraHeaders) + }); + return getContent(r); +} + +async function requestJson(url, params = {}, extraHeaders = {}) { + const text = await requestText(url, params, extraHeaders); + if (!text) return null; + return JSON.parse(text); +} + +async function requestJsonSafe(url, params = {}, extraHeaders = {}) { + try { + return await requestJson(url, params, extraHeaders); + } catch (e) { + return null; + } +} + +async function requestTextSafe(url, params = {}, extraHeaders = {}) { + try { + return await requestText(url, params, extraHeaders); + } catch (e) { + return ''; + } +} + +function isOk(data) { + return data && ( + data.code === 200 || + data.code === '200' || + data.status === 200 || + data.status === '200' || + data.success === true || + data.data !== undefined + ); +} + +function pick(obj, keys = [], fallback = '') { + if (!obj || typeof obj !== 'object') return fallback; + for (const key of keys) { + if (obj[key] !== undefined && obj[key] !== null && obj[key] !== '') { + return obj[key]; + } + } + return fallback; +} + +function todayString() { + const d = new Date(); + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + +function uniqueByVodId(list = []) { + const seen = new Set(); + const result = []; + list.forEach(item => { + if (!item || !item.vod_id) return; + const key = String(item.vod_id); + if (seen.has(key)) return; + seen.add(key); + result.push(item); + }); + return result; +} + +function encodeLiveId(item = {}) { + try { + return 'live|' + encodeURIComponent(JSON.stringify(item)); + } catch (e) { + return 'live|' + encodeURIComponent(JSON.stringify({ + id: pick(item, ['id', 'match_id', 'matchId', 'room_id', 'roomId']) + })); + } +} + +function decodeLiveId(id) { + const raw = String(id || ''); + if (!raw.startsWith('live|')) return null; + try { + return JSON.parse(decodeURIComponent(raw.slice(5))); + } catch (e) { + return null; + } +} + +function isMediaUrl(url) { + const value = safeText(url).toLowerCase(); + if (!value) return false; + return value.includes('.m3u8') || + value.includes('m3u8?') || + value.includes('.flv') || + value.includes('flv?') || + value.includes('.mp4') || + value.includes('mp4?') || + value.startsWith('rtmp') || + value.startsWith('rtsp') || + value.startsWith('p2p') || + value.startsWith('mitv'); +} + +function isLivePageUrl(url) { + const value = safeText(url).toLowerCase(); + return value.includes('/pc/room/') || value.includes('/room/'); +} + +function normalizeTypeValue(tid) { + const raw = String(tid || ''); + if (raw === 'live') return ''; + if (raw === 'live_hot') return ''; + if (raw === 'live_football') return '1'; + if (raw === 'live_basketball') return '2'; + if (raw === 'live_tennis') return '3'; + if (raw === 'live_billiards') return '4'; + if (raw === 'live_nba') return ''; + if (raw === 'all') return ''; + if (raw === 'football') return '1'; + if (raw === 'basketball') return '2'; + if (raw === 'nba') return ''; + return raw; +} + +function joinTitle(home, away, league) { + const h = safeText(home, ''); + const a = safeText(away, ''); + const l = safeText(league, '赛事'); + + if (h && a) return `${l} ${h} vs ${a}`; + if (h) return `${l} ${h}`; + if (a) return `${l} ${a}`; + return l; +} + +// ========== 分类配置 ========== + +const classes = [ + { type_id: 'live', type_name: '全部直播' }, + { type_id: 'live_hot', type_name: '热门直播' }, + { type_id: 'live_football', type_name: '足球直播' }, + { type_id: 'live_basketball', type_name: '篮球直播' }, + { type_id: 'live_nba', type_name: 'NBA直播' }, + { type_id: 'live_tennis', type_name: '网球直播' }, + { type_id: 'live_billiards', type_name: '台球直播' }, + { type_id: 'all', type_name: '全部录像' }, + { type_id: 'football', type_name: '足球录像' }, + { type_id: 'basketball', type_name: '篮球录像' }, + { type_id: 'nba', type_name: 'NBA录像' } +]; + +const leagueAll = [ + { n: '全部', v: '' }, + { n: 'NBA', v: 'NBA' }, + { n: 'CBA', v: 'CBA' }, + { n: '英超', v: '英超' }, + { n: '西甲', v: '西甲' }, + { n: '意甲', v: '意甲' }, + { n: '德甲', v: '德甲' }, + { n: '法甲', v: '法甲' }, + { n: '欧冠', v: '欧冠' }, + { n: '中超', v: '中超' }, + { n: '中乙', v: '中乙' } +]; + +const filters = { + live: [{ key: 'league', name: '联赛', value: leagueAll }], + live_hot: [{ key: 'league', name: '联赛', value: leagueAll }], + live_football: [{ key: 'league', name: '联赛', value: leagueAll }], + live_basketball: [{ key: 'league', name: '联赛', value: leagueAll }], + live_nba: [{ key: 'league', name: '联赛', value: [{ n: 'NBA', v: 'NBA' }] }], + all: [{ key: 'league', name: '联赛', value: leagueAll }], + football: [{ key: 'league', name: '联赛', value: leagueAll }], + basketball: [{ key: 'league', name: '联赛', value: leagueAll }], + nba: [{ key: 'league', name: '联赛', value: [{ n: 'NBA', v: 'NBA' }] }] +}; + +// ========== 通用数据提取 ========== + +function findBestArray(node, depth = 0) { + if (!node || depth > 6) return []; + if (Array.isArray(node)) return node; + if (typeof node !== 'object') return []; + + const keys = [ + 'list', 'items', 'rows', 'data', 'records', + 'matches', 'matchList', 'schedules', 'scheduleList', + 'lives', 'liveList', 'rooms', 'roomList', + 'events', 'result' + ]; + + for (const key of keys) { + if (Array.isArray(node[key])) return node[key]; + const arr = findBestArray(node[key], depth + 1); + if (arr.length) return arr; + } + + for (const value of Object.values(node)) { + const arr = findBestArray(value, depth + 1); + if (arr.length) return arr; + } + + return []; +} + +function getDataArray(data) { + if (!data) return []; + if (Array.isArray(data)) return data; + if (isOk(data)) return findBestArray(data.data); + return findBestArray(data); +} + +function getLeagueText(item) { + return safeText(pick(item, [ + 'league_name', 'leagueName', 'league', + 'competition_name', 'competitionName', + 'event_name', 'eventName', + 'match_type', 'matchType' + ]), ''); +} + +function getSportTypeText(item) { + return safeText(pick(item, [ + 'type', 'type_id', 'typeId', + 'sport_type', 'sportType', + 'category_id', 'categoryId', + 'sport', 'category' + ]), ''); +} + +function liveTitleText(item = {}) { + const title = safeText(pick(item, ['title', 'name', 'match_name', 'matchName'], '')); + const league = getLeagueText(item); + const home = safeText(pick(item, ['home_team', 'homeTeam', 'home_name', 'homeName', 'team1', 'home'], '')); + const away = safeText(pick(item, ['away_team', 'awayTeam', 'away_name', 'awayName', 'team2', 'away'], '')); + return [title, league, home, away].filter(Boolean).join(' '); +} + +function isLiveItem(item) { + if (!item || typeof item !== 'object') return false; + + const status = safeText(pick(item, [ + 'status', 'match_status', 'matchStatus', + 'state', 'live_status', 'liveStatus', + 'status_text', 'statusText' + ])).toLowerCase(); + + const liveFlag = pick(item, ['is_live', 'isLive', 'live', 'living'], ''); + const hasLiveFlag = liveFlag === true || String(liveFlag) === '1'; + + const hasRoom = !!pick(item, [ + 'room_id', 'roomId', 'roomid', + 'live_room_id', 'liveRoomId', + 'room_url', 'roomUrl' + ], ''); + + const hasPlayUrl = !!firstPlayableUrl(item); + + if (hasLiveFlag || hasPlayUrl || hasRoom) return true; + if (status.includes('live')) return true; + if (status.includes('正在')) return true; + if (status.includes('直播')) return true; + if (status === '1' || status === '2') return true; + + return false; +} + +function filterLiveItems(items = [], league = '', type = '', isHot = false) { + const leagueText = safeText(league).toLowerCase(); + const typeText = safeText(type); + + return items.filter(item => { + if (!item || typeof item !== 'object') return false; + if (!isLiveItem(item)) return false; + + const title = liveTitleText(item); + const titleLower = title.toLowerCase(); + const itemLeague = getLeagueText(item).toLowerCase(); + const itemType = getSportTypeText(item); + + if (leagueText) { + if (!itemLeague.includes(leagueText) && !titleLower.includes(leagueText)) { + return false; + } + } + + if (isHot) { + const hotValue = pick(item, ['is_hot', 'isHot', 'hot', 'recommend', 'is_recommend'], ''); + const hotOk = hotValue === true || String(hotValue) === '1' || title.includes('热门'); + if (!hotOk) return false; + } + + if (typeText === '1') { + const ok = itemType === '1' || + itemType.includes('足球') || + title.includes('足球') || + title.includes('英超') || + title.includes('西甲') || + title.includes('意甲') || + title.includes('德甲') || + title.includes('法甲') || + title.includes('欧冠') || + title.includes('中超') || + title.includes('中乙'); + if (!ok) return false; + } + + if (typeText === '2') { + const ok = itemType === '2' || + itemType.includes('篮球') || + title.includes('篮球') || + title.includes('NBA') || + title.includes('CBA') || + title.includes('WNBA'); + if (!ok) return false; + } + + if (typeText === '3') { + const ok = itemType === '3' || itemType.includes('网球') || title.includes('网球'); + if (!ok) return false; + } + + if (typeText === '4') { + const ok = itemType === '4' || itemType.includes('台球') || title.includes('台球'); + if (!ok) return false; + } + + return true; + }); +} + +// ========== 直播接口 ========== + +async function fetchLiveByApi(page = 1, size = 30, league = '', type = '', isHot = false) { + const date = todayString(); + + const urls = [ + `${host}/api/v1/live`, + `${host}/api/v1/lives`, + `${host}/api/v1/live/list`, + `${host}/api/v1/lives/list`, + `${host}/api/v1/live/matches`, + `${host}/api/v1/matches/live`, + `${host}/api/v1/rooms`, + `${host}/api/v1/room/list`, + `${host}/api/v1/schedule`, + `${host}/api/v1/schedules`, + `${host}/api/v1/schedule/today`, + `${host}/api/v1/matches` + ]; + + const paramList = [ + { page, size, limit: size, league, type, date }, + { page, pageSize: size, league, type, date }, + { page, per_page: size, league, type, date }, + { page, size, league, type, status: 'live' }, + { page, size, league, type, live: 1 }, + { page, size, league, type, is_live: 1 } + ]; + + for (const url of urls) { + for (const params of paramList) { + const cleanParams = {}; + Object.keys(params).forEach(key => { + if (params[key] !== undefined && params[key] !== null && params[key] !== '') { + cleanParams[key] = params[key]; + } + }); + + const data = await requestJsonSafe(url, cleanParams, { + Referer: host + '/pc/live' + }); + + const arr = getDataArray(data); + if (!arr.length) continue; + + const filtered = filterLiveItems(arr, league, type, isHot); + if (filtered.length) return filtered.slice(0, size); + } + } + + return []; +} + +function parseLiveCardsFromHtml(html = '') { + const text = safeText(html); + if (!text || text === '加载中...') return []; + + const list = []; + const roomReg = /\/pc\/room\/(\d+)/g; + let m; + + while ((m = roomReg.exec(text)) !== null) { + const roomId = m[1]; + const start = Math.max(0, m.index - 800); + const end = Math.min(text.length, m.index + 800); + const block = text.slice(start, end); + + const titleMatch = block.match(/([\u4e00-\u9fa5A-Za-z0-9\s]+?\s+vs\s+[\u4e00-\u9fa5A-Za-z0-9\s]+)/i); + const title = titleMatch ? titleMatch[1].replace(/\s+/g, ' ').trim() : `直播房间 ${roomId}`; + + const imgMatch = block.match(/https?:\/\/[^"'<>]+?\.(?:jpg|jpeg|png|webp)/i); + const pic = imgMatch ? imgMatch[0] : ''; + + list.push({ + id: roomId, + room_id: roomId, + room_url: `${host}/pc/room/${roomId}`, + title: title, + cover: pic, + status_text: 'LIVE', + is_live: 1 + }); + } + + return list; +} + +async function fetchLiveByPage(page = 1, size = 30, league = '', type = '', isHot = false) { + const html = await requestTextSafe(`${host}/pc/live`, {}, { + Referer: host + '/pc/live', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + }); + + const arr = parseLiveCardsFromHtml(html); + return filterLiveItems(arr, league, type, isHot).slice(0, size); +} + +async function fetchLiveMatches(page = 1, size = 30, league = '', type = '', isHot = false) { + let list = await fetchLiveByApi(page, size, league, type, isHot); + + if (!list.length) { + list = await fetchLiveByPage(page, size, league, type, isHot); + } + + return list; +} + +async function fetchLiveDetail(roomOrMatchId) { + const id = safeText(roomOrMatchId); + if (!id) return null; + + const urls = [ + `${host}/api/v1/room/${encodeURIComponent(id)}`, + `${host}/api/v1/rooms/${encodeURIComponent(id)}`, + `${host}/api/v1/live/${encodeURIComponent(id)}`, + `${host}/api/v1/lives/${encodeURIComponent(id)}`, + `${host}/api/v1/match/${encodeURIComponent(id)}/live`, + `${host}/api/v1/match/${encodeURIComponent(id)}/streams`, + `${host}/api/v1/matches/${encodeURIComponent(id)}`, + `${host}/api/v1/match/${encodeURIComponent(id)}` + ]; + + for (const url of urls) { + const data = await requestJsonSafe(url, {}, { + Referer: `${host}/pc/room/${encodeURIComponent(id)}` + }); + + if (!data) continue; + if (isOk(data)) return data.data; + if (typeof data === 'object') return data; + } + + return null; +} + +// ========== 录像接口 ========== + +async function fetchRecordings(page = 1, size = 30, league = '', type = '') { + const params = { page, size }; + + if (league) { + params.league = league; + } else if (type && type !== 'all' && type !== 'nba') { + params.type = type; + } + + return await requestJsonSafe(`${host}/api/v1/recordings`, params, { + Referer: host + '/pc/replay' + }); +} + +async function fetchMatchRecordings(matchId) { + return await requestJsonSafe(`${host}/api/v1/match/${encodeURIComponent(matchId)}/recordings`, {}, { + Referer: host + '/pc/replay' + }); +} + +// ========== 播放源提取 ========== + +function addPlayable(arr, name, url) { + const value = cleanPlayUrl(url); + if (!value) return; + const line = `${cleanName(name)}$${value}`; + if (arr.indexOf(line) >= 0) return; + arr.push(line); +} + +function collectPlayableLinks(node, prefix = '线路', arr = [], depth = 0) { + if (!node || depth > 7) return arr; + + if (typeof node === 'string') { + if (isMediaUrl(node)) { + addPlayable(arr, prefix, node); + } + return arr; + } + + if (Array.isArray(node)) { + node.forEach((item, idx) => { + collectPlayableLinks(item, `${prefix}${idx + 1}`, arr, depth + 1); + }); + return arr; + } + + if (typeof node !== 'object') return arr; + + const name = cleanName(pick(node, [ + 'name', 'title', 'line', 'line_name', 'lineName', + 'source_name', 'sourceName', 'quality', 'label' + ], prefix)); + + const directKeys = [ + 'play_url', 'playUrl', + 'video_url', 'videoUrl', + 'stream_url', 'streamUrl', + 'live_url', 'liveUrl', + 'm3u8', 'flv', 'hls', + 'url', 'src', 'link', + 'hd', 'sd', 'uhd', + 'source', 'stream' + ]; + + directKeys.forEach(key => { + const value = node[key]; + if (typeof value === 'string' && isMediaUrl(value)) { + addPlayable(arr, name, value); + } + }); + + Object.keys(node).forEach(key => { + if ([ + 'cover', 'cover_image', 'coverImage', + 'logo', 'home_team_logo', 'away_team_logo', + 'homeLogo', 'awayLogo', 'avatar' + ].includes(key)) { + return; + } + collectPlayableLinks(node[key], name, arr, depth + 1); + }); + + return arr; +} + +function firstPlayableUrl(item) { + const arr = collectPlayableLinks(item, '线路', [], 0); + if (!arr.length) return ''; + const parts = arr[0].split('$'); + return parts.length > 1 ? parts.slice(1).join('$') : ''; +} + +function buildLivePlayLine(liveItem = {}, detailData = null) { + const arr = []; + + collectPlayableLinks(liveItem, '线路', arr); + collectPlayableLinks(detailData, '线路', arr); + + if (!arr.length) { + const roomId = safeText(pick(liveItem, [ + '_live_room_id', + 'room_id', 'roomId', 'roomid', + 'live_room_id', 'liveRoomId', + 'id', 'match_id', 'matchId' + ], '')); + + const pageUrl = safeText(pick(liveItem, [ + '_live_page_url', + 'room_url', 'roomUrl', + 'page_url', 'pageUrl', + 'link' + ], '')); + + const finalPage = pageUrl || (roomId ? `${host}/pc/room/${encodeURIComponent(roomId)}` : `${host}/pc/live`); + addPlayable(arr, '打开直播间', fixUrl(finalPage)); + } + + return arr.join('#'); +} + +function buildEpisodeLine(items = [], prefix = '录像') { + const arr = []; + if (!Array.isArray(items)) return ''; + + items.forEach((rec, idx) => { + const url = cleanPlayUrl(pick(rec, [ + 'video_url', 'videoUrl', + 'url', + 'play_url', 'playUrl' + ], '')); + + if (!url) return; + + const name = cleanName(pick(rec, ['title', 'name'], `${prefix}${idx + 1}`)); + arr.push(`${name}$${url}`); + }); + + return arr.join('#'); +} + +// ========== 列表解析 ========== + +function parseLiveList(items = []) { + if (!Array.isArray(items)) return []; + + return items.map(item => { + const titleRaw = safeText(pick(item, ['title', 'name', 'match_name', 'matchName'], '')); + const league = safeText(getLeagueText(item), '直播'); + + const home = safeText(pick(item, [ + 'home_team', 'homeTeam', + 'home_name', 'homeName', + 'team1', 'home' + ], '')); + + const away = safeText(pick(item, [ + 'away_team', 'awayTeam', + 'away_name', 'awayName', + 'team2', 'away' + ], '')); + + const title = titleRaw || joinTitle(home, away, league); + + const time = safeText(pick(item, [ + 'start_time', 'startTime', + 'match_time', 'matchTime', + 'time' + ], '正在直播')); + + const roomId = safeText(pick(item, [ + 'room_id', 'roomId', 'roomid', + 'live_room_id', 'liveRoomId', + 'id', 'match_id', 'matchId' + ], '')); + + const status = safeText(pick(item, [ + 'status_text', 'statusText', + 'status', 'match_status', 'matchStatus', + 'state' + ], 'LIVE')); + + let pic = pick(item, [ + 'cover_image', 'coverImage', + 'cover', 'thumb', 'thumbnail', + 'home_team_logo', 'homeLogo', + 'away_team_logo', 'awayLogo', + 'logo', 'pic' + ], ''); + + pic = fixUrl(pic); + + const liveItem = Object.assign({}, item, { + _live_title: title, + _live_league: league, + _live_time: time, + _live_room_id: roomId, + _live_page_url: roomId ? `${host}/pc/room/${encodeURIComponent(roomId)}` : fixUrl(pick(item, ['room_url', 'roomUrl', 'link'], '')) + }); + + return { + vod_id: encodeLiveId(liveItem), + vod_name: title, + vod_pic: pic, + vod_remarks: `🔴 ${status || 'LIVE'} | ${time}` + }; + }).filter(v => v.vod_id && v.vod_name); +} + +function parseVideoList(items = [], showScore = true) { + if (!Array.isArray(items)) return []; + + return items.map(item => { + const home = safeText(pick(item, [ + 'home_team', 'homeTeam', + 'home_name', 'homeName', + 'team1', 'home' + ], '主队')); + + const away = safeText(pick(item, [ + 'away_team', 'awayTeam', + 'away_name', 'awayName', + 'team2', 'away' + ], '客队')); + + const league = safeText(pick(item, [ + 'league_name', 'leagueName', + 'league', + 'competition_name', 'competitionName' + ], '赛事')); + + const title = joinTitle(home, away, league); + + const homeScore = safeNumber(pick(item, ['home_score', 'homeScore', 'score_home'], '-')); + const awayScore = safeNumber(pick(item, ['away_score', 'awayScore', 'score_away'], '-')); + const score = `${homeScore} - ${awayScore}`; + + const time = safeText(pick(item, [ + 'start_time', 'startTime', + 'match_time', 'matchTime', + 'time' + ], '')); + + const count = safeNumber(pick(item, [ + 'recording_count', 'recordingCount', + 'replay_count', 'video_count' + ], '0')); + + let pic = pick(item, [ + 'cover_image', 'coverImage', + 'cover', + 'home_team_logo', 'homeLogo', + 'away_team_logo', 'awayLogo', + 'logo', 'pic' + ], ''); + + pic = fixUrl(pic); + + if (pic.includes('default_cover')) { + pic = fixUrl(pick(item, [ + 'home_team_logo', 'homeLogo', + 'away_team_logo', 'awayLogo', + 'logo' + ], '')); + } + + return { + vod_id: String(pick(item, ['match_id', 'matchId', 'id'], '')), + vod_name: title, + vod_pic: pic, + vod_remarks: showScore ? `${score} | ${time} | ${count}个录像` : time + }; + }).filter(v => v.vod_id && v.vod_name); +} + +// ========== 壳子接口 ========== + +async function init(cfg) { + return JSON.stringify({}); +} + +async function home(filter) { + try { + return JSON.stringify({ + class: classes, + filters: filters + }); + } catch (e) { + return JSON.stringify({ class: [] }); + } +} + +async function homeVod() { + try { + const liveItems = await fetchLiveMatches(1, 30, '', '', false); + const liveList = parseLiveList(liveItems); + + const data = await fetchRecordings(1, 20); + const replayList = isOk(data) ? parseVideoList(getDataArray(data)) : []; + + return JSON.stringify({ + list: uniqueByVodId([].concat(liveList, replayList)) + }); + } catch (e) { + return JSON.stringify({ list: [] }); + } +} + +async function category(tid, pg, filter, extend = {}) { + try { + const page = parseInt(pg) || 1; + const size = 30; + let league = ''; + let type = normalizeTypeValue(tid); + const isHot = String(tid || '') === 'live_hot'; + + if (extend && extend.league) { + league = extend.league; + } else if (String(tid || '') === 'live_nba' || String(tid || '') === 'nba') { + league = 'NBA'; + } + + if (String(tid || '').startsWith('live')) { + const liveItems = await fetchLiveMatches(page, size, league, type, isHot); + const list = parseLiveList(liveItems); + const pagecount = list.length >= size ? page + 1 : page; + + return JSON.stringify({ + page: page, + pagecount: pagecount, + limit: size, + total: list.length, + list: list + }); + } + + const data = await fetchRecordings(page, size, league, type); + const list = isOk(data) ? parseVideoList(getDataArray(data)) : []; + const pagecount = list.length >= size ? page + 1 : page; + + return JSON.stringify({ + page: page, + pagecount: pagecount, + limit: size, + total: list.length, + list: list + }); + } catch (e) { + return JSON.stringify({ + page: parseInt(pg) || 1, + pagecount: 0, + limit: 0, + total: 0, + list: [] + }); + } +} + +async function detail(id) { + try { + const rawId = String(id || '').trim(); + if (!rawId) return JSON.stringify({ list: [] }); + + const liveItem = decodeLiveId(rawId); + + if (liveItem) { + const roomId = safeText(pick(liveItem, [ + '_live_room_id', + 'room_id', 'roomId', 'roomid', + 'live_room_id', 'liveRoomId', + 'id', 'match_id', 'matchId' + ], '')); + + const detailData = roomId ? await fetchLiveDetail(roomId) : null; + + const title = safeText(pick(liveItem, [ + '_live_title', + 'title', 'name', 'match_name', 'matchName' + ], '正在直播')); + + const league = safeText(pick(liveItem, [ + '_live_league', + 'league_name', 'leagueName', + 'league', + 'competition_name', 'competitionName' + ], '直播')); + + const time = safeText(pick(liveItem, [ + '_live_time', + 'start_time', 'startTime', + 'match_time', 'matchTime', + 'time' + ], '正在直播')); + + const pic = fixUrl(pick(liveItem, [ + 'cover_image', 'coverImage', + 'cover', 'thumb', 'thumbnail', + 'home_team_logo', 'homeLogo', + 'away_team_logo', 'awayLogo', + 'logo', 'pic' + ], '')); + + const playLine = buildLivePlayLine(liveItem, detailData); + + const vodInfo = { + vod_id: rawId, + vod_name: title, + vod_pic: pic, + vod_remarks: '正在直播', + vod_content: `${league} ${title},比赛时间:${time}`, + vod_play_from: playLine ? '正在直播' : '', + vod_play_url: playLine, + type_name: league + }; + + return JSON.stringify({ list: [vodInfo] }); + } + + const matchId = rawId; + const data = await fetchMatchRecordings(matchId); + if (!isOk(data)) return JSON.stringify({ list: [] }); + + const root = data.data || {}; + const match = root.match || {}; + const replays = root.replays || []; + const highlights = root.highlights || []; + + const home = safeText(pick(match, [ + 'home_team', 'homeTeam', + 'home_name', 'homeName', + 'team1', 'home' + ], '主队')); + + const away = safeText(pick(match, [ + 'away_team', 'awayTeam', + 'away_name', 'awayName', + 'team2', 'away' + ], '客队')); + + const league = safeText(pick(match, [ + 'league_name', 'leagueName', + 'league', + 'competition_name', 'competitionName' + ], '赛事')); + + const title = joinTitle(home, away, league); + + const score = `${safeNumber(pick(match, ['home_score', 'homeScore', 'score_home'], '-'))} - ${safeNumber(pick(match, ['away_score', 'awayScore', 'score_away'], '-'))}`; + + const time = safeText(pick(match, [ + 'start_time', 'startTime', + 'match_time', 'matchTime', + 'time' + ], '')); + + const round = safeText(pick(match, ['match_round', 'matchRound', 'round'], '')); + + const playFrom = []; + const playUrls = []; + + const replayLine = buildEpisodeLine(replays, '录像'); + if (replayLine) { + playFrom.push('全场录像'); + playUrls.push(replayLine); + } + + const highlightLine = buildEpisodeLine(highlights, '集锦'); + if (highlightLine) { + playFrom.push('比赛集锦'); + playUrls.push(highlightLine); + } + + const pic = fixUrl(pick(match, [ + 'cover_image', 'coverImage', + 'cover', + 'home_team_logo', 'homeLogo', + 'away_team_logo', 'awayLogo', + 'logo', 'pic' + ], '')); + + const vodInfo = { + vod_id: matchId, + vod_name: title, + vod_pic: pic, + vod_remarks: score, + vod_content: `${league} ${round} ${home} vs ${away},比分 ${score},比赛时间:${time}`, + vod_play_from: playFrom.join('$$$'), + vod_play_url: playUrls.join('$$$'), + type_name: league + }; + + return JSON.stringify({ list: [vodInfo] }); + } catch (e) { + return JSON.stringify({ list: [] }); + } +} + +async function search(wd, quick, pg = 1) { + try { + const keyword = safeText(wd).toLowerCase(); + if (!keyword) { + return JSON.stringify({ + page: 1, + pagecount: 0, + list: [] + }); + } + + const liveItems = await fetchLiveMatches(1, 100); + const filteredLive = liveItems.filter(item => { + return liveTitleText(item).toLowerCase().includes(keyword); + }); + const liveList = parseLiveList(filteredLive); + + const data = await fetchRecordings(1, 100); + let replayList = []; + + if (isOk(data)) { + const arr = getDataArray(data); + const filteredReplay = arr.filter(item => { + const title = [ + pick(item, ['title', 'name', 'match_name', 'matchName'], ''), + pick(item, ['home_team', 'homeTeam', 'home_name', 'homeName'], ''), + pick(item, ['away_team', 'awayTeam', 'away_name', 'awayName'], ''), + pick(item, ['league_name', 'leagueName', 'league'], '') + ].join(' ').toLowerCase(); + + return title.includes(keyword); + }); + + replayList = parseVideoList(filteredReplay); + } + + const list = uniqueByVodId([].concat(liveList, replayList)); + + return JSON.stringify({ + page: parseInt(pg) || 1, + pagecount: 1, + limit: list.length, + total: list.length, + list: list + }); + } catch (e) { + return JSON.stringify({ + page: parseInt(pg) || 1, + pagecount: 0, + list: [] + }); + } +} + +async function play(flag, id, flags) { + try { + const url = cleanPlayUrl(id); + const needParse = isLivePageUrl(url) && !isMediaUrl(url); + + return JSON.stringify({ + parse: needParse ? 1 : 0, + url: url, + header: { + 'User-Agent': headers['User-Agent'], + 'Referer': isLivePageUrl(url) ? host + '/pc/live' : host, + 'Origin': host + } + }); + } catch (e) { + return JSON.stringify({ + parse: 0, + url: id || '' + }); + } +} + +export default { + init: init, + home: home, + homeVod: homeVod, + category: category, + detail: detail, + search: search, + play: play +}; \ No newline at end of file diff --git a/js/红果果短剧.js b/js/红果果短剧.js new file mode 100644 index 0000000..320e22b --- /dev/null +++ b/js/红果果短剧.js @@ -0,0 +1,29 @@ +var rule = { + title: '红果果短剧', + host: 'https://www.hongguoguo.tv/', + url: '/vod/show/id/fyclass/page/fyage.html', + searchUrl: '/vod/search/page/fypage/wd/**.html', + class_name: '精选短剧&穿越短剧&古装短剧&逆袭短剧&都市短剧', + class_url: 'jingxuanduanju&chuanyueduanju&guzhuangduanju&nixiduanju&dushiduanju', + searchable: 2, + quickSearch: 0, + filterable: 0, + headers: { + 'User-Agent': 'MOBILE_UA', + }, + play_parse: true, + lazy: "js:\n let html = request(input);\n let hconf = html.match(/r player_.*?=(.*?) { + "全部" !== e.name && a.push({ + n: e.name, + v: e.name + }) + }); + let c = [{ + n: "全部", + v: "" + }]; + ["2026", "2025", "2024", "2023", "2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010"].forEach(e => { + c.push({ + n: e, + v: e + }) + }); + let p = [{ + n: "全部", + v: "全部" + }]; + for (let e = 65; e <= 90; e++) p.push({ + n: String.fromCharCode(e), + v: String.fromCharCode(e) + }); + for (let e = 0; e <= 9; e++) p.push({ + n: e.toString(), + v: e.toString() + }); + let l = [{ + n: "最新", + v: "time" + }, { + n: "最热", + v: "hot" + }, { + n: "评分", + v: "score" + }]; + const d = { + 1: "电影", + 2: "连续剧", + 3: "综艺", + 4: "动漫" + }; + for (const e in d) { + const t = d[e]; + let o = [], + s = [{ + n: "全部", + v: e + }]; + Array.isArray(r[t]) && r[t].forEach(e => { + "全部" !== e.name && s.push({ + n: e.name, + v: e.id.toString() + }) + }), o.push({ + key: "cateId", + name: "类型", + init: e, + value: s + }); + let u = [{ + n: "全部", + v: "全部" + }]; + Array.isArray(n[t]) && n[t].forEach(e => { + "全部" !== e.name && u.push({ + n: e.name, + v: e.name + }) + }), o.push({ + key: "area", + name: "地区", + init: "全部", + value: u + }), o.push({ + key: "lang", + name: "语言", + init: "全部", + value: a + }), o.push({ + key: "year", + name: "年份", + init: "", + value: c + }), o.push({ + key: "letter", + name: "字母", + init: "全部", + value: p + }), o.push({ + key: "orderBy", + name: "排序", + init: "time", + value: l + }), i[e] = o + } + t = i + } + return JSON.stringify({ + class: n, + filters: t || {} + }) + } catch (e) { + return JSON.stringify({ + class: n, + filters: {} + }) + } +} +async function c() { + try { + const e = `${r}/v1/home-list`, + t = await req(e, { + headers: o, + timeout: 8e3 + }); + if (!t || !t.content) return JSON.stringify({ + list: [] + }); + let n = "string" == typeof t.content ? JSON.parse(t.content) : t.content, + a = new Map; + for (const e in n) Array.isArray(n[e]) && n[e].forEach(e => { + if (e && e.VodID) { + const t = e.VodID.toString(); + a.has(t) || a.set(t, { + vod_id: t, + vod_name: e.VodName, + vod_pic: e.VodPic, + vod_remarks: e.VodRemarks + }) + } + }); + const s = Array.from(a.values()); + return JSON.stringify({ + list: s + }) + } catch (e) { + return JSON.stringify({ + list: [] + }) + } +} +async function p(e, t, n, a) { + try { + if ("home" === e) { + let e = JSON.parse(await c()); + return JSON.stringify({ + list: e.list || [], + page: 1, + pagecount: 1 + }) + } + const n = a.cateId ? encodeURIComponent(a.cateId) : e, + s = "全部", + i = a.area ? encodeURIComponent(a.area) : "全部", + p = a.lang ? encodeURIComponent(a.lang) : "全部", + l = a.year ? encodeURIComponent(a.year) : "", + d = a.letter ? encodeURIComponent(a.letter) : "全部", + u = a.orderBy ? encodeURIComponent(a.orderBy) : "time", + g = `${r}/v1/vod-list?orderBy=${u}&type=${n}&class=${s}&area=${i}&year=${l}&lang=${p}&letter=${d}&page=${t}`, + f = await req(g, { + headers: o, + timeout: 8e3 + }); + if (!f || !f.content) return JSON.stringify({ + list: [], + page: parseInt(t), + pagecount: parseInt(t) + }); + let y = "string" == typeof f.content ? JSON.parse(f.content) : f.content; + if (0 !== y.status || !y.data) return JSON.stringify({ + list: [], + page: parseInt(t), + pagecount: parseInt(t) + }); + const m = y.data.map(e => ({ + vod_id: e.VodID.toString(), + vod_name: e.VodName, + vod_pic: e.VodPic, + vod_remarks: e.VodRemarks + })), + h = y.qty || 0, + v = h > 0 ? Math.ceil(h / 60) : parseInt(t); + return JSON.stringify({ + list: m, + page: parseInt(t), + pagecount: v + }) + } catch (e) { + return JSON.stringify({ + list: [], + page: parseInt(t), + pagecount: parseInt(t) + }) + } +} +async function l(e, t, n = 1) { + try { + const t = encodeURIComponent(e), + a = `${r}/v1/search?keyword=${t}&cate=undefined&page=${n}`, + s = await req(a, { + headers: o, + timeout: 8e3 + }); + if (!s || !s.content) return JSON.stringify({ + list: [], + page: parseInt(n), + pagecount: parseInt(n) + }); + let i = "string" == typeof s.content ? JSON.parse(s.content) : s.content; + if (0 !== i.status || !i.data) return JSON.stringify({ + list: [], + page: parseInt(n), + pagecount: parseInt(n) + }); + const c = i.data.map(e => ({ + vod_id: e.VodID.toString(), + vod_name: e.VodName, + vod_pic: e.VodPic, + vod_remarks: e.VodRemarks + })), + p = i.qty || 0, + l = p > 0 ? Math.ceil(p / 60) : parseInt(n); + return JSON.stringify({ + list: c, + page: parseInt(n), + pagecount: l + }) + } catch (e) { + return JSON.stringify({ + list: [], + page: parseInt(n), + pagecount: parseInt(n) + }) + } +} +async function d(e) { + try { + const t = `${r}/v1/vod-details?id=${e}`, + n = await req(t, { + headers: o, + timeout: 8e3 + }); + if (!n || !n.content) return JSON.stringify({ + list: [] + }); + let a = "string" == typeof n.content ? JSON.parse(n.content) : n.content; + if (0 !== a.status || !a.vod) return JSON.stringify({ + list: [] + }); + const s = a.vod, + i = s.VodPlayServer || [], + c = s.VodPlayUrls || {}; + let p = [], + l = []; + i.forEach(e => { + if (1 === e.Status) { + const t = e.From, + n = e.Show, + r = c[t] || []; + if (r.length > 0) { + const e = r.map(e => `${e[0]}$${e[1]}`).join("#"); + p.push(n), l.push(e) + } + } + }); + const d = p.join("$$$"), + u = l.join("$$$"), + g = { + vod_id: s.VodID.toString(), + vod_name: s.VodName, + vod_pic: s.VodPic, + type_name: a.VodClass ? a.VodClass.TypeName : "", + vod_year: s.VodYear || "", + vod_area: s.VodArea || "", + vod_remarks: s.VodRemarks || "", + vod_actor: s.VodActor || "", + vod_director: s.VodDirector || "", + vod_content: s.VodContent ? s.VodContent.replace(/<\/?[^>]+(>|$)/g, "") : "", + vod_play_from: d, + vod_play_url: u + }; + return JSON.stringify({ + list: [g] + }) + } catch (e) { + return JSON.stringify({ + list: [] + }) + } +} +async function u(t, r, a) { + try { + const t = /^(https?:)?\/\//i.test(r) ? r : `https://vip.jsjinfu.com:8443/?url=${r}`; + let a = await async function(t, n) { + let r = { + "User-Agent": o["User-Agent"], + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "accept-language": "zh-CN,zh;q=0.9", + "cache-control": "no-cache", + pragma: "no-cache", + referer: n, + "upgrade-insecure-requests": "1" + }; + try { + const n = (await req(t, { + headers: r + })).content, + a = h(n, "video_url"); + if (a) return a; + const s = h(n, "Domain"), + i = h(n, "Time"), + c = h(n, "Version"), + p = h(n, "Vurl"), + l = h(n, "Vkey"), + d = h(n, "Key"), + u = h(n, "Ref"), + S = s || "vip.jsjinfu.com"; + if (!(i && p && l && d)) return null; + const $ = md5X(l); + let _ = y(d, $), + N = g.encryptToBase64(d, _); + const k = s + S + i + p + d + _; + let O = m(md5X(k)), + C = f.encode(O, "971a0e7224fecb61b1868f1211a6360d"), + A = y(C, $), + I = g.encryptToBase64(C, A), + V = m(md5X(k + C + A)), + b = f.encode(V, i + "52bc46dec47a199abc82793bfabe56f6"), + J = y(b, $), + w = g.encryptToBase64(b, J), + U = `${l}-${d}-${C}-${b}`, + E = `${_}-${A}-${J}`; + const D = U + E; + let R = g.encryptToBase64(D + N + I + w, S + s + i), + B = { + url: p, + wap: "0", + ios: "0", + host: S, + referer: u, + time: i, + key: d, + key1: _, + sign: C, + sign1: A, + token: b, + token1: J + }, + X = function(t, n) { + let r = e.enc.Latin1.parse(n.substring(0, 16)), + o = e.enc.Latin1.parse(n.substring(16, 32)); + return e.AES.encrypt(e.enc.Base64.stringify(e.enc.Utf8.parse(t)), r, { + iv: o, + mode: e.mode.CBC, + padding: e.pad.Pkcs7 + }).toString() + }(JSON.stringify(B), md5X(R)); + B.ckey = "110#" + e.enc.Base64.stringify(e.enc.Latin1.parse(X)); + let q = function(e) { + let t = String(e.time), + n = e.key, + r = e.key1, + o = e.sign1, + a = e.token1, + s = ["2", "6", "2", "4"], + i = t.split("")[s[0]] || "e", + c = n.split("")[s[1]] || "t", + p = r.split("")[s[2]] || "c", + l = o.split("")[o.split("").length - s[3]] || "n", + d = a.split(""), + u = []; + for (let e = 0; 2 * e < d.length; e++) switch (u.push(d[d.length - e - 1]), e < d.length - e - 1 && u.push(a[e]), e) { + case 1: + u.push(i); + break; + case 2: + u.push(c); + break; + case 3: + u.push(p); + break; + case 4: + u.push(l) + } + return u.join("") + }(B), + T = 1024 * i, + j = `https://${s}:8443/Api.php?ver=${c}×tamp=${T}&appkey=${md5X(S+T+c)}`, + x = { + "User-Agent": o["User-Agent"], + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + Vkey: l, + Md5: md5X(D + R + q), + Version: c, + "Access-Token0": U, + "Access-Token1": E, + "Access-Token2": R, + "Access-Token3": q, + Origin: `https://${S}:8443`, + Referer: t + }; + const M = await req(j, { + method: "post", + headers: x, + data: v(B), + postType: "form" + }); + let P = "string" == typeof M.content ? JSON.parse(M.content) : M.content; + if (P && 1 === P.Status) { + let t = P.Appkey + P.Md5 + P.Version, + n = md5X(t), + r = e.enc.Latin1.parse(n.substring(0, 16)), + o = e.enc.Latin1.parse(n.substring(16, 32)), + a = e.AES.decrypt(P.Data, r, { + iv: o, + mode: e.mode.CBC, + padding: e.pad.Pkcs7 + }), + s = JSON.parse(e.enc.Utf8.stringify(a)), + i = f.decode(s.url, md5X(S + b)); + return decodeURIComponent(i) + } + } catch (e) {} + return null + }(t, `${n}/`); + if (a) return JSON.stringify({ + parse: 0, + url: a, + header: { + "User-Agent": o["User-Agent"] + } + }) + } catch (e) {} + return JSON.stringify({ + parse: 1, + url: r, + header: {} + }) +} +const g = (e => { + if (!e) throw new Error(""); + const t = (e, t) => { + const n = e.words, + r = e.sigBytes, + o = r + 3 >> 2, + a = new Array(t ? o + 1 : o); + for (let e = 0; e < r; e++) a[e >> 2] |= (n[e >> 2] >>> 24 - e % 4 * 8 & 255) << ((3 & e) << 3); + return t && (a[o] = r), a + }, + n = (e, t, n, r, o, a) => (n >>> 5 ^ t << 2) + (t >>> 3 ^ n << 4) ^ (e ^ t) + (a[3 & r ^ o] ^ n); + return { + encryptToBase64(r, o) { + if (!r || 0 === r.length) return r; + const a = t(e.enc.Utf8.parse(r), !0), + s = t(e.enc.Utf8.parse(o), !1); + return e.enc.Base64.stringify((t => { + const n = t.length, + r = []; + for (let e = 0; e < n; e++) { + const n = t[e]; + r[e] = (255 & n) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 + } + return e.lib.WordArray.create(r, 4 * n) + })(((e, t) => { + t.length < 4 && (t.length = 4); + const r = e.length, + o = r - 1; + let a, s = e[o], + i = 0; + for (let c = 0 | Math.floor(6 + 52 / r); c > 0; --c) { + i = i + 2654435769 & 4294967295; + const r = i >>> 2 & 3; + for (let c = 0; c < o; ++c) a = e[c + 1], s = e[c] = e[c] + n(i, a, s, c, r, t) & 4294967295; + a = e[0], s = e[o] = e[o] + n(i, a, s, o, r, t) & 4294967295 + } + return e + })(a, s))) + } + } + })(void 0 !== e ? e : null), + f = function() { + const t = () => Math.floor(Date.now() / 1e3), + n = function(n, r = "DECODE", o = "liangcheng", a = 0) { + o = md5X(o); + const s = md5X(o.substring(0, 16)), + i = md5X(o.substring(16, 32)); + let c = ""; + if ("DECODE" === r) c = n.substring(0, 4); + else { + const e = md5X((() => { + const e = Date.now(), + t = Math.floor(e / 1e3); + return (e - 1e3 * t) / 1e3 + " " + t + })()); + c = e.substring(e.length - 4) + } + const p = e.enc.Utf8.parse(s + md5X(s + c)); + let l = ""; + if ("DECODE" === r) { + let r = e.enc.Base64.parse(n.substring(4)), + o = e.RC4.decrypt({ + ciphertext: r + }, p); + l = e.enc.Latin1.stringify(o); + const a = parseInt(l.substring(0, 10), 10), + s = 0 === a || a - t() > 0, + c = l.substring(10, 26) === md5X(l.substring(26) + i).substring(0, 16); + return s && c ? l.substring(26) : "" + } { + a = a ? a + t() : 0; + const r = String(a).padStart(10, "0") + md5X(n + i).substring(0, 16) + n; + return l = e.RC4.encrypt(e.enc.Latin1.parse(r), p).toString(), c + l.replace(/=/g, "") + } + }; + return { + encode: (e, t, r) => n(e, "ENCODE", t, r).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "."), + decode: (e, t) => n(e.replace(/-/g, "+").replace(/_/g, "/").replace(/\./g, "="), "DECODE", t) + } + }(); + +function y(t, n) { + return e.AES.encrypt(e.enc.Utf8.parse(t), e.enc.Utf8.parse(n), { + mode: e.mode.ECB + }).ciphertext.toString() +} + +function m(e) { + return md5X(`17325841932717338791732584194271733878${e}`.replace(/[\-|\,]/g, "")) +} + +function h(e, t) { + const n = e.match(new RegExp(`var\\s+${t}\\s*=\\s*['"]([^'"]+)['"]`)); + return n ? n[1] : null +} + +function v(e) { + let t = {}; + for (let n in e) t[n] = a ? encodeURIComponent(e[n] || "") : e[n]; + return t +} +export function __jsEvalReturn() { + return { + init: s, + home: i, + homeVod: c, + category: p, + search: l, + detail: d, + play: u + } +} \ No newline at end of file diff --git a/js/麻豆.js b/js/麻豆.js new file mode 100644 index 0000000..7f059b4 --- /dev/null +++ b/js/麻豆.js @@ -0,0 +1,250 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '麻豆HD', + lang: 'cat' +}) +*/ + +import { load } from 'assets://js/lib/cat.js'; + +let host = 'https://www.madouhd.com'; +let headers = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 16; 23078RKD5C) AppleWebKit/537.36', + 'Referer': host +}; + +async function request(url, options = {}) { + try { + let res = await req(url, { headers: { ...headers, ...options.headers }, ...options }); + return res.content || ''; + } catch (e) { + console.log('[请求失败] ' + url); + return ''; + } +} + +function buildCategoryUrl(tid, page, extend) { + let cls = (extend.class || '').trim(); + let year = (extend.year || '').trim(); + let hasFilter = cls || year; + + if (!hasFilter) { + return `${host}/vodtype/${tid}-${page}.html`; + } + + let clsPart = cls ? encodeURIComponent(cls) : ''; + let yearPart = year ? year : ''; + + let base = `${host}/vodshow/${tid}---${clsPart}-----${page}`; + if (yearPart) { + base += `---${yearPart}`; + } + return base + '.html'; +} + +function yearOptions() { + let arr = [{ n: '全部', v: '' }]; + for (let y = 2026; y >= 2004; y--) arr.push({ n: y.toString(), v: y.toString() }); + return arr; +} + +function home(filter) { + const classes = [ + { type_id: '1', type_name: '电影' }, + { type_id: '2', type_name: '电视剧' }, + { type_id: '3', type_name: '综艺' }, + { type_id: '4', type_name: '动漫' }, + { type_id: '24', type_name: '纪录片' } + ]; + + const filters = { + '1': [ + { key: 'class', name: '剧情', value: [ + { n: '全部', v: '' }, { n: '动作', v: '动作' }, { n: '喜剧', v: '喜剧' }, + { n: '爱情', v: '爱情' }, { n: '科幻', v: '科幻' }, { n: '恐怖', v: '恐怖' }, + { n: '剧情', v: '剧情' }, { n: '战争', v: '战争' }, { n: '犯罪', v: '犯罪' } + ]}, + { key: 'year', name: '年份', value: yearOptions() } + ], + '2': [ + { key: 'class', name: '剧情', value: [ + { n: '全部', v: '' }, { n: '古装', v: '古装' }, { n: '爱情', v: '爱情' }, + { n: '奇幻', v: '奇幻' }, { n: '喜剧', v: '喜剧' }, { n: '家庭', v: '家庭' } + ]}, + { key: 'year', name: '年份', value: yearOptions() } + ], + '3': [{ key: 'year', name: '年份', value: yearOptions() }], + '4': [{ key: 'year', name: '年份', value: yearOptions() }], + '24': [{ key: 'year', name: '年份', value: yearOptions() }] + }; + return JSON.stringify({ class: classes, filters: filters }); +} + +async function homeVod() { + return JSON.stringify({ list: [] }); +} + +async function category(tid, pg, filter, extend) { + let page = parseInt(pg) || 1; + let url = buildCategoryUrl(tid, page, extend || {}); + console.log('[分类] ' + url); + + let html = await request(url); + if (!html) return JSON.stringify({ list: [], page, pagecount: 1 }); + + const $ = load(html); + const list = []; + + $('.stui-vodlist li').each((i, el) => { + let a = $(el).find('a.stui-vodlist__thumb'); + let href = a.attr('href') || ''; + let title = a.attr('title') || ''; + let pic = a.attr('data-original') || a.find('img').attr('src') || ''; + let remarks = a.find('.pic-text').text().trim(); + let vid = (href.match(/\/(\d+)\.html$/) || [])[1] || href; + + if (title && vid) { + list.push({ + vod_id: vid, + vod_name: title, + vod_pic: pic, + vod_remarks: remarks + }); + } + }); + + let totalPage = 1; + $('a').each((i, el) => { + let href = $(el).attr('href') || ''; + let m = href.match(/\/(\d+)(?:-(\d+)|-----(\d+)---\d+)?\.html$/); + if (m) { + let p = parseInt(m[2] || m[3] || m[1]); + if (p > totalPage) totalPage = p; + } + }); + if (totalPage < page && list.length > 0) totalPage = page + 1; + + return JSON.stringify({ + list, + page: page, + pagecount: totalPage, + limit: list.length, + total: totalPage * list.length + }); +} + +async function detail(id) { + let url = `${host}/dy/${id}.html`; + let html = await request(url); + if (!html) return JSON.stringify({ list: [] }); + + const $ = load(html); + let vod = { + vod_id: id, + vod_name: $('h1.title').text().trim(), + vod_pic: $('.stui-content__thumb img').attr('data-original') || $('.stui-content__thumb img').attr('src') || '', + vod_content: '', + vod_play_from: '', + vod_play_url: '', + vod_director: '', + vod_actor: '', + type_name: '', + vod_area: '', + vod_year: '', + vod_remarks: '' + }; + + let dataText = $('.stui-content__detail .data').first().text() || ''; + let typeMatch = dataText.match(/类型:(.+?)(?=\s*(?:地区|年份|$))/); + if (typeMatch) vod.type_name = typeMatch[1].replace(/\s+/g, ' ').trim(); + let areaMatch = dataText.match(/地区:(.+?)(?=\s*(?:年份|$))/); + if (areaMatch) vod.vod_area = areaMatch[1].trim(); + let yearMatch = dataText.match(/年份:(\d{4})/); + if (yearMatch) vod.vod_year = yearMatch[1]; + $('.stui-content__detail .data').each((i, el) => { + let text = $(el).text(); + if (text.includes('导演:')) + vod.vod_director = $(el).find('a').map((j, a) => $(a).text()).get().join(','); + if (text.includes('主演:')) + vod.vod_actor = $(el).find('a').map((j, a) => $(a).text()).get().join(','); + }); + + vod.vod_content = $('.detail-content').text().trim() || $('.detail-sketch').text().trim(); + + let updateMatch = $('.stui-content__detail').text().match(/更新:(\d{4}-\d{2}-\d{2})/); + if (updateMatch) vod.vod_remarks = updateMatch[1]; + + let sources = []; + $('.stui-pannel-box.b.playlist.mb').each((i, panel) => { + let name = $(panel).find('h3.title').text().trim(); + let urls = []; + $(panel).find('.stui-content__playlist a').each((j, a) => { + let href = $(a).attr('href') || ''; + let text = $(a).text().trim(); + if (!href.startsWith('http')) href = host + href; + urls.push(text + '$' + href); + }); + if (urls.length) sources.push({ name, urls: urls.join('#') }); + }); + + if (sources.length) { + vod.vod_play_from = sources.map(s => s.name).join('$$$'); + vod.vod_play_url = sources.map(s => s.urls).join('$$$'); + } else { + let btn = $('.play-btn .btn-primary').attr('href'); + if (btn) { + if (!btn.startsWith('http')) btn = host + btn; + vod.vod_play_from = '播放'; + vod.vod_play_url = '立即观看$' + btn; + } + } + + return JSON.stringify({ list: [vod] }); +} + +async function search(wd, quick, pg) { + let page = pg || 1; + let url = `${host}/vodsearch/-------------.html?wd=${encodeURIComponent(wd)}&submit=`; + let html = await request(url); + if (!html) return JSON.stringify({ list: [] }); + + const $ = load(html); + const list = []; + $('.stui-vodlist li').each((i, el) => { + let a = $(el).find('a.stui-vodlist__thumb'); + let href = a.attr('href') || ''; + let title = a.attr('title') || ''; + let pic = a.attr('data-original') || a.find('img').attr('src') || ''; + let remarks = a.find('.pic-text').text().trim(); + let vid = (href.match(/\/(\d+)\.html$/) || [])[1] || href; + if (title && vid) list.push({ vod_id: vid, vod_name: title, vod_pic: pic, vod_remarks: remarks }); + }); + return JSON.stringify({ list }); +} + +async function play(flag, id, flags) { + let url = id.startsWith('http') ? id : host + id; + let html = await request(url); + if (!html) return JSON.stringify({ parse: 0, url: url }); + + let match = html.match(/player_aaaa\s*=\s*(\{[\s\S]*?\});/); + if (match) { + try { + let cfg = JSON.parse(match[1]); + if (cfg.url) return JSON.stringify({ parse: 1, url: cfg.url, header: JSON.stringify({ Referer: url }) }); + } catch (e) {} + let urlMatch = match[1].match(/"url":"([^"]+)"/); + if (urlMatch) { + let realUrl = urlMatch[1].replace(/\\\//g, '/'); + return JSON.stringify({ parse: 1, url: realUrl, header: JSON.stringify({ Referer: url }) }); + } + } + return JSON.stringify({ parse: 0, url: url }); +} + +export function __jsEvalReturn() { + return { init: () => {}, home, homeVod, category, detail, play, search }; +} \ No newline at end of file