Skip to content

Commit 6b6d8e6

Browse files
committed
更新爬虫文件
1 parent b9d7614 commit 6b6d8e6

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

spider/catvod/嗷呜动漫[漫].js

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/*
2+
title: '嗷呜动漫', author: '小可乐/v6.1.1'
3+
说明:可以不写ext,也可以写ext,ext支持的参数和格式参数如下
4+
"ext": {
5+
"host": "xxxx", //站点网址
6+
"timeout": 6000 //请求超时,单位毫秒
7+
}
8+
*/
9+
import {Crypto} from 'assets://js/lib/cat.js';
10+
11+
const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
12+
const DefHeader = {'User-Agent': MOBILE_UA};
13+
var HOST;
14+
var KParams = {
15+
headers: {'User-Agent': MOBILE_UA},
16+
timeout: 5000
17+
};
18+
19+
async function init(cfg) {
20+
try {
21+
HOST = (cfg.ext?.host?.trim() || 'https://www.aowu.tv').replace(/\/$/, '');
22+
KParams.headers['Referer'] = HOST;
23+
let parseTimeout = parseInt(cfg.ext?.timeout?.trim(), 10);
24+
KParams.timeout = parseTimeout > 0 ? parseTimeout : 5000;
25+
} catch (e) {
26+
console.error('初始化参数失败:', e.message);
27+
}
28+
}
29+
30+
async function home(filter) {
31+
try {
32+
let kclassName = '新番$20&番剧$21&剧场$22';
33+
let classes = kclassName.split('&').map(item => {
34+
let [cName, cId] = item.split('$');
35+
return {type_name: cName, type_id: cId};
36+
});
37+
let filters = {};
38+
try {
39+
const nameObj = { class: 'class,剧情', year: 'year,年份', by: 'by,排序' };
40+
const flValues = { class: ['搞笑','恋爱','校园','后宫','治愈','日常','原创','战斗','百合','BL','卖肉','漫画改','游戏改','异世界','泡面番','轻小说改','OVA','OAD','京阿尼','芳文社','A-1Pictures','CloverWorks','J.C.STAFF','动画工房','SUNRISE','Production.I.G','MADHouse','BONES','P.A.WORKS','SHAFT','MAPPA','ufotable','TRIGGER','WITSTUDIO'], year: ['2026','2025','2024','2023','2022','2021','2020','2019','2018','2017','2016','2015','2014','2013','2012','2011','2010','2009','2008','2007','2006','2005','2004','2003','2002','2001','2000','1999','1998','1997','1996','1995','1994','1993','1992','1991','1990'], by: ['按最新,time', '按最热,hits', '按评分,score'] };
41+
for (let item of classes) {
42+
filters[item.type_id] = Object.entries(nameObj).map(([nObjk, nObjv]) => {
43+
let [kkey, kname] = nObjv.split(',');
44+
let fvalue = flValues[nObjk] || [];
45+
if (item.type_id === '20' && nObjk === 'year') {fvalue = fvalue.slice(0, 2);}
46+
let kvalue = fvalue.map(it => {
47+
let [n, v] = [it, it];
48+
if (nObjk === 'by') {[n, v] = it.split(',');}
49+
return {n: n, v: v};
50+
});
51+
if (nObjk !== 'by') {kvalue.unshift({n: '全部', v: ''});}
52+
return {key: kkey, name: kname, value: kvalue};
53+
}).filter(flt => flt.key && flt.value.length > 1);
54+
}
55+
} catch (e) {
56+
filters = {};
57+
}
58+
return JSON.stringify({class: classes, filters: filters});
59+
} catch (e) {
60+
console.error('获取分类失败:', e.message);
61+
return JSON.stringify({class: [], filters: {}});
62+
}
63+
}
64+
65+
async function homeVod() {
66+
try {
67+
let homeUrl = HOST;
68+
let resHtml = await request(homeUrl);
69+
let VODS = getVodList(resHtml, true);
70+
return JSON.stringify({list: VODS});
71+
} catch (e) {
72+
console.error('推荐页获取失败:', e.message);
73+
return JSON.stringify({list: []});
74+
}
75+
}
76+
77+
async function category(tid, pg, filter, extend) {
78+
try {
79+
pg = parseInt(pg, 10);
80+
pg = pg > 0 ? pg : 1;
81+
let cateBody = `type=${tid}&class=${extend?.class ?? ''}&year=${extend?.year ?? ''}&by=${extend?.by ?? ''}&page=${pg}`;
82+
let cateUrl = `${HOST}/index.php/ds_api/vod`;
83+
let resObj = safeParseJSON(await request(cateUrl, {
84+
headers: {...KParams.headers, 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'},
85+
method: 'POST',
86+
body: cateBody
87+
}));
88+
if (!resObj) {throw new Error('源码对象为空');}
89+
let VODS = [];
90+
let listArr = Array.isArray(resObj.list) ? resObj.list : [];
91+
for (let it of listArr) {
92+
let kname = it.vod_name || '名称';
93+
let kpic = it.vod_pic || '图片';
94+
let kremarks = `${it.vod_remarks || '状态'}|${it.vod_douban_score || '无评分'}`;
95+
let kyear = extend?.year || '';
96+
let kid = it.url ?? 'Id';
97+
VODS.push({
98+
vod_name: kname,
99+
vod_pic: kpic,
100+
vod_remarks: kremarks,
101+
vod_year: kyear,
102+
vod_id: `${kid}@${kname}@${kpic}@${kremarks}`
103+
});
104+
}
105+
let {pagecount=1000, limit=30, total=30000} = resObj;
106+
return JSON.stringify({list: VODS, page: pg, pagecount: pagecount, limit: 30, total: total});
107+
} catch (e) {
108+
console.error('类别页获取失败:', e.message);
109+
return JSON.stringify({list: [], page: 1, pagecount: 0, limit: 30, total: 0});
110+
}
111+
}
112+
113+
async function search(wd, quick, pg) {
114+
try {
115+
pg = parseInt(pg, 10);
116+
pg = pg > 0 ? pg : 1;
117+
let searchUrl = `${HOST}/search/${wd}----------${pg}---.html`;
118+
let resHtml = await request(searchUrl);
119+
let VODS = getVodList(resHtml);
120+
return JSON.stringify({list: VODS, page: pg, pagecount: 10, limit: 30, total: 300});
121+
} catch (e) {
122+
console.error('搜索页获取失败:', e.message);
123+
return JSON.stringify({list: [], page: 1, pagecount: 0, limit: 30, total: 0});
124+
}
125+
}
126+
127+
function getVodList(khtml, rec = false) {
128+
try {
129+
if (!khtml) {throw new Error('源码为空');}
130+
let kvods = [];
131+
let selector = rec ? '.public-list-box' : '.search-list';
132+
let listArr = pdfa(khtml, selector);
133+
for (let it of listArr) {
134+
let kname = cutStr(it, 'alt="', '"', '名称');
135+
let kpic = cutStr(it, 'data-src="', '"', '图片');
136+
let kremarks = rec ? `${cutStr(it, 'public-prt£>', '<', '类型')}|${cutStr(it, 'ft2">', '<', '状态')}` : cutStr(it, 'this-wap">', '</div>', '状态');
137+
let kid = cutStr(it, 'href="', '"', 'Id');
138+
kvods.push({
139+
vod_name: kname,
140+
vod_pic: kpic,
141+
vod_remarks: kremarks,
142+
vod_id: `${kid}@${kname}@${kpic}@${kremarks}`
143+
});
144+
}
145+
return kvods;
146+
} catch (e) {
147+
console.error(`生成视频列表失败:`, e.message);
148+
return [];
149+
}
150+
}
151+
152+
async function detail(ids) {
153+
try {
154+
let [id, kname, kpic, kremarks] = ids.split('@');
155+
let detailUrl = !/^http/.test(id) ? `${HOST}${id}` : id;
156+
let resHtml = await request(detailUrl);
157+
if (!resHtml) {throw new Error('源码为空');}
158+
let intros = cutStr(resHtml, 'search-show', '</ul>', '', false);
159+
let ktabs = pdfa(resHtml, '.anthology-tab&&a').map((it,idx) => cutStr(it, '</i>', '<', `线路${idx+1}`));
160+
let kurls = pdfa(resHtml, '.anthology-list-play').map(item => {
161+
return pdfa(item, 'a').map(it => { return `${cutStr(it, '>', '<', 'noEpi')}$${cutStr(it, 'href="', '"', 'noUrl')}` }).join('#');
162+
});
163+
let VOD = {
164+
vod_id: detailUrl,
165+
vod_name: kname,
166+
vod_pic: kpic,
167+
type_name: cutStr(intros, '类型:', '</li>', '类型'),
168+
vod_remarks: `${cutStr(intros, '状态:', '</li>', '状态')}|${cutStr(intros, '更新:', '</li>', '更新')}`,
169+
vod_year: cutStr(intros, '年份:', '</li>', '1000'),
170+
vod_area: cutStr(intros, '地区:', '</li>', '地区'),
171+
vod_lang: cutStr(intros, '语言:', '</li>', '语言'),
172+
vod_director: cutStr(intros, '导演:', '</li>', '').replace(/,$/, '') || '导演',
173+
vod_actor: cutStr(intros, '主演:', '</li>', '').replace(/,$/, '') || '主演',
174+
vod_content: cutStr(intros, '简介:', '</li>', '') || kname,
175+
vod_play_from: ktabs.join('$$$'),
176+
vod_play_url: kurls.join('$$$')
177+
};
178+
return JSON.stringify({list: [VOD]});
179+
} catch (e) {
180+
console.error('详情页获取失败:', e.message);
181+
return JSON.stringify({list: []});
182+
}
183+
}
184+
185+
async function play(flag, ids, flags) {
186+
try {
187+
let playUrl = !/^http/.test(ids) ? `${HOST}${ids}` : ids;
188+
let kp = 0, kurl = '';
189+
let resHtml = await request(playUrl);
190+
let codeObj = safeParseJSON(cutStr(resHtml, 'var player_£=', '<', '', false));
191+
let jurl = codeObj?.url ?? '';
192+
jurl = safeUrlDecode(safeB64Decode(jurl));
193+
if (jurl) {
194+
jurl = `${HOST}/player/?url=${jurl}&next=`;
195+
resHtml = await request(jurl);
196+
let encryptedUrl = cutStr(resHtml, 'const encryptedUrl = "', '"', '');
197+
let sessionKey = cutStr(resHtml, 'const sessionKey = "', '"', '');
198+
kurl = urlAesDecrypt(encryptedUrl, sessionKey);
199+
}
200+
if (!/^http/.test(kurl)) {
201+
kurl = playUrl;
202+
kp = 1;
203+
}
204+
return JSON.stringify({jx: 0, parse: kp, url: kurl, header: DefHeader});
205+
} catch (e) {
206+
console.error('播放失败:', e.message);
207+
return JSON.stringify({jx: 0, parse: 0, url: '', header: {}});
208+
}
209+
}
210+
211+
function urlAesDecrypt(ciphertext, key) {
212+
try {
213+
const rawData = Crypto.enc.Base64.parse(ciphertext);
214+
const keyWordArr = Crypto.enc.Utf8.parse(key);
215+
const ivWordArr = Crypto.lib.WordArray.create(rawData.words.slice(0, 4));
216+
const encrypted = Crypto.lib.WordArray.create(rawData.words.slice(4));
217+
const decrypted = Crypto.AES.decrypt( { ciphertext: encrypted }, keyWordArr,
218+
{
219+
iv: ivWordArr,
220+
mode: Crypto.mode.CBC,
221+
padding: Crypto.pad.Pkcs7
222+
}
223+
);
224+
return decrypted.toString(Crypto.enc.Utf8);
225+
} catch (e) {
226+
return '';
227+
}
228+
}
229+
230+
function safeB64Decode(b64Str) {
231+
try {return Crypto.enc.Utf8.stringify(Crypto.enc.Base64.parse(b64Str));} catch (e) {return '';}
232+
}
233+
234+
function safeUrlDecode(urlStr) {
235+
try {return decodeURIComponent(urlStr);} catch (e) {return '';}
236+
}
237+
238+
function safeParseJSON(jStr) {
239+
try {return JSON.parse(jStr);} catch (e) {return null;}
240+
}
241+
242+
function cutStr(str, prefix = '', suffix = '', defaultVal = 'cutFaile', clean = true, i = 1, all = false) {
243+
try {
244+
if (typeof str !== 'string' || !str) {throw new Error('被截取对象需为非空字符串');}
245+
const cleanStr = cs => String(cs).replace(/<[^>]*?>/g, ' ').replace(/(&nbsp;|\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' ');
246+
const esc = s => String(s).replace(/[.*+?${}()|[\]\\/^]/g, '\\$&');
247+
let pre = esc(prefix).replace(/£/g, '[^]*?'), end = esc(suffix);
248+
let regex = new RegExp(`${pre ? pre : '^'}([^]*?)${end ? end : '$'}`, 'g');
249+
let matchIterator = str.matchAll(regex);
250+
if (all) {
251+
let matchArr = [...matchIterator];
252+
return matchArr.length ? matchArr.map(it => {
253+
const val = it[1] ?? defaultVal;
254+
return clean && val !== defaultVal ? cleanStr(val) : val;
255+
}) : [defaultVal];
256+
}
257+
i = parseInt(i, 10);
258+
if (isNaN(i) || i < 1) {throw new Error('序号必须为正整数');}
259+
let tgIdx = i - 1,matchIdx = 0;
260+
for (const match of matchIterator) {
261+
if (matchIdx++ === tgIdx) {
262+
const result = match[1] ?? defaultVal;
263+
return clean && result !== defaultVal ? cleanStr(result) : result;
264+
}
265+
}
266+
return defaultVal;
267+
} catch (e) {
268+
console.error(`字符串截取失败:`, e.message);
269+
return all ? ['cutErr'] : 'cutErr';
270+
}
271+
}
272+
273+
async function request(reqUrl, options = {}) {
274+
try {
275+
if (typeof reqUrl !== 'string' || !reqUrl.trim()) { throw new Error('reqUrl需为字符串且非空'); }
276+
if (typeof options !== 'object' || Array.isArray(options) || options === null) { throw new Error('options类型需为非null对象'); }
277+
options.method = options.method?.toUpperCase() || 'GET';
278+
if (['GET', 'HEAD'].includes(options.method)) {
279+
delete options.body;
280+
delete options.data;
281+
delete options.postType;
282+
}
283+
let {headers, timeout, buffer, ...restOpts} = options;
284+
const optObj = {
285+
headers: (typeof headers === 'object' && !Array.isArray(headers) && headers) ? headers : KParams.headers,
286+
timeout: parseInt(timeout, 10) > 0 ? parseInt(timeout, 10) : KParams.timeout,
287+
buffer: buffer ?? 0,
288+
...restOpts
289+
};
290+
const res = await req(reqUrl, optObj);
291+
if (options.withHeaders) {
292+
const resHeaders = typeof res.headers === 'object' && !Array.isArray(res.headers) && res.headers ? res.headers : {};
293+
const resWithHeaders = { ...resHeaders, body: res?.content ?? '' };
294+
return JSON.stringify(resWithHeaders);
295+
}
296+
return res?.content ?? '';
297+
} catch (e) {
298+
console.error(`${reqUrl}→请求失败:`, e.message);
299+
return options?.withHeaders ? JSON.stringify({ body: '' }) : '';
300+
}
301+
}
302+
303+
export function __jsEvalReturn() {
304+
return {
305+
init,
306+
home,
307+
homeVod,
308+
category,
309+
search,
310+
detail,
311+
play,
312+
proxy: null
313+
};
314+
}

0 commit comments

Comments
 (0)