From f8934175e2a90168224b609be957a3ebd4ec4a11 Mon Sep 17 00:00:00 2001 From: "Wang.Luo" <1593775941@qq.com> Date: Tue, 16 Sep 2025 01:52:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(adult):=20=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=88=90=E4=BA=BA=E5=86=85=E5=AE=B9=E6=BA=90=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=87=87=E9=9B=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增奇优福利、稀饭资源等成人内容源 - 添加采集之王功能,支持批量采集成人内容 - 新增 javxbb 网站支持 - 优化成人内容的搜索和播放功能 - 更新 adult.json 配置,增加新的内容源和功能 --- XBPQ/adult/奇优福利.json | 31 +++ XBPQ/adult/稀饭资源.json | 56 ++++++ adult.json | 54 ++++++ js/adult/奇优福利.json | 31 +++ js/adult/稀饭资源.json | 56 ++++++ js/adult/采集福利.json$1$1 | 378 +++++++++++++++++++++++++++++++++++++ py/adult/javxbb.py | 214 +++++++++++++++++++++ 7 files changed, 820 insertions(+) create mode 100644 XBPQ/adult/奇优福利.json create mode 100644 XBPQ/adult/稀饭资源.json create mode 100644 js/adult/奇优福利.json create mode 100644 js/adult/稀饭资源.json create mode 100644 js/adult/采集福利.json$1$1 create mode 100644 py/adult/javxbb.py diff --git a/XBPQ/adult/奇优福利.json b/XBPQ/adult/奇优福利.json new file mode 100644 index 0000000..c6c3d3a --- /dev/null +++ b/XBPQ/adult/奇优福利.json @@ -0,0 +1,31 @@ + { + + "作者":"艾丝沐", + + "站点":"奇优影院", + + "请求头": "手机", + "主页url":"http://www.qiyoudy2.com/", + "简介":"&&", + "数组":"
&&", + "图片":"data-original=\"&&\"", + "标题":"title=\"&&\"", + "副标题":"text-right\">&&", + "链接":"href=\"&&\"", + "搜索url":"http://www.qiyoudy2.com/search.php;post;searchword={wd}", + "搜索数组":"v-thumb stui-vodlist__thumb&&", + "搜索图片":"data-original=\"&&\"", + "搜索标题":"title=\"&&\"", + "搜索副标题":"text-right\">&&", + "搜索链接":"href=\"&&\"", + "线路数组":"data-toggle=\"tab\"&&", + "线路标题":">&&", + "播放数组":"stui-content__playlist clearfix&&", + "播放列表":"
  • &&
  • ", + "播放标题":">&&", + "嗅探词":".m3u8#.mp4#.flv#.mp3#.m4a", + "分类url":"http://www.qiyoudy2.com/list/{cateId}_{catePg}.html;;ak", + "分类":"🔞福利推荐$6" + + } + \ No newline at end of file diff --git a/XBPQ/adult/稀饭资源.json b/XBPQ/adult/稀饭资源.json new file mode 100644 index 0000000..63c9f87 --- /dev/null +++ b/XBPQ/adult/稀饭资源.json @@ -0,0 +1,56 @@ +{ +"作者": "艾丝沐/2409/第一版", +"站名": "稀饭福利", +"请求头": "User-Agent$MOBILE_UA", +"编码": "UTF-8", +"图片代理": "0", + +"主页url": "https://app.4kwo.com/api.php/provide/home_data?id=8", +"首页": "120", +"起始页": "1", +"分类url": "/api.php/provide/vod_list?id=8&type={cateId}&area=&year={year}&order={by}&page={catePg};;mrcd0", +"分类": "精品推荐&三级伦理", +"分类值": "*", + +"二次截取": "默认--空||首页--hotvideo&&msg\":\\[", +"数组": "{&&}", +"标题": "name\":\"&&\"", +"图片": "img\":\"&&\"", +"副标题": "qingxidu\":\"&&\"", +"链接": "/api.php/provide/vod_detail?ac=vod_detail&id=+id\":&&,", + +"影片年代": "msg\":\"&&\"", +"影片地区": "vod_area\":\"&&\"", +"影片类型": "type\":\"&&\"", +"状态": "remarks\":\"&&\"", +"导演": "director\":\"&&\"", +"主演": "actor\":\"&&\"", +"简介": "\"info\":\"&&\"", + +"线路数组": "show\":&&,", +"线路标题": "🔞+\"&&\"", +"播放二次截取": "playlist\":&&,[替换:$$$>>接表组组表题#\">>接表组表题#$>>题接#\\#>>接表表题]", +"播放数组": "组&&组", +"播放列表": "表&&表", +"播放标题": "题&&题", +"播放链接": "接&&接", + +"直接播放": "0", +"嗅探词": ".mp4#.m3u8", + +"搜索请求头": "User-Agent$MOBILE_UA", +"搜索url": "/api.php/provide/search_result?video_name={wd}", +"搜索模式": "1", +"搜索二次截取": "search_result\":\\[&&\\]", +"搜索数组": "{&&}", +"搜索标题": "video_name\":\"&&\"", +"搜索图片": "img\":\"&&\"", +"搜索副标题": "\"\":\"&&\"", +"搜索链接": "/api.php/provide/vod_detail?ac=vod_detail&id=+id\":&&,", + +"筛选": "1", +"年份": "1949-2025", +"年份值": "*", +"排序": "为你推荐&最新&评分&最热", +"排序值": "空&new&score&hits" +} \ No newline at end of file diff --git a/adult.json b/adult.json index d658b5c..921d359 100644 --- a/adult.json +++ b/adult.json @@ -55,6 +55,13 @@ "现代都市" ] }, + { + "key": "福利采集", + "name": "福利合集", + "type": 3, + "api": "./js/drpy2.min.js", + "ext": "./js/adult/采集之王.js?type=url¶ms=../js/adult/采集福利.json$1$1" + }, { "key": "红牛资源", "name": "红牛|伦理", @@ -128,6 +135,26 @@ "api": "csp_XBPQ", "ext": "./XBPQ/adult/jable.json" }, + { + "key": "稀饭资源", + "name": "宝盒福利", + "type": 3, + "api": "csp_XBPQ", + "searchable": 1, + "quickSearch": 1, + "filterable": 1, + "ext": "./XBPQ/adult/稀饭资源.json" + }, + { + "key": "奇优福利", + "name": "奇优福利", + "type": 3, + "api": "csp_XBPQ", + "searchable": 1, + "quickSearch": 1, + "filterable": 1, + "ext": "./XBPQ/adult/奇优福利.json" + }, { "key": "四虎", "name": "四虎影院", @@ -901,6 +928,24 @@ "伦理片" ] }, + { + "key": "8002", + "name": "悦妠资源", + "type": 1, + "api": "https://vnzyz.com/api.php/provide/vod/at/json/", + "searchable": 1, + "quickSearch": 1, + "filterable": 1 + }, + { + "key": "8003", + "name": "湿乐园资源", + "type": 1, + "api": "https://xxavs.com/api.php/provide/vod/at/json/", + "searchable": 1, + "quickSearch": 1, + "filterable": 1 + }, { "key": "国产情色AV", "name": "国产情色AV", @@ -1216,6 +1261,15 @@ "searchable": 1, "quickSearch": 1, "filterable": 1 + }, + { + "key": "javxbb", + "name": "javxbb", + "type": 3, + "api": ".py/adult/javxbb.py", + "searchable": 1, + "quickSearch": 1, + "filterable": 1 } ], "lives": [ diff --git a/js/adult/奇优福利.json b/js/adult/奇优福利.json new file mode 100644 index 0000000..c6c3d3a --- /dev/null +++ b/js/adult/奇优福利.json @@ -0,0 +1,31 @@ + { + + "作者":"艾丝沐", + + "站点":"奇优影院", + + "请求头": "手机", + "主页url":"http://www.qiyoudy2.com/", + "简介":"&&
    ", + "数组":"
    &&", + "图片":"data-original=\"&&\"", + "标题":"title=\"&&\"", + "副标题":"text-right\">&&", + "链接":"href=\"&&\"", + "搜索url":"http://www.qiyoudy2.com/search.php;post;searchword={wd}", + "搜索数组":"v-thumb stui-vodlist__thumb&&", + "搜索图片":"data-original=\"&&\"", + "搜索标题":"title=\"&&\"", + "搜索副标题":"text-right\">&&", + "搜索链接":"href=\"&&\"", + "线路数组":"data-toggle=\"tab\"&&", + "线路标题":">&&", + "播放数组":"stui-content__playlist clearfix&&", + "播放列表":"
  • &&
  • ", + "播放标题":">&&", + "嗅探词":".m3u8#.mp4#.flv#.mp3#.m4a", + "分类url":"http://www.qiyoudy2.com/list/{cateId}_{catePg}.html;;ak", + "分类":"🔞福利推荐$6" + + } + \ No newline at end of file diff --git a/js/adult/稀饭资源.json b/js/adult/稀饭资源.json new file mode 100644 index 0000000..63c9f87 --- /dev/null +++ b/js/adult/稀饭资源.json @@ -0,0 +1,56 @@ +{ +"作者": "艾丝沐/2409/第一版", +"站名": "稀饭福利", +"请求头": "User-Agent$MOBILE_UA", +"编码": "UTF-8", +"图片代理": "0", + +"主页url": "https://app.4kwo.com/api.php/provide/home_data?id=8", +"首页": "120", +"起始页": "1", +"分类url": "/api.php/provide/vod_list?id=8&type={cateId}&area=&year={year}&order={by}&page={catePg};;mrcd0", +"分类": "精品推荐&三级伦理", +"分类值": "*", + +"二次截取": "默认--空||首页--hotvideo&&msg\":\\[", +"数组": "{&&}", +"标题": "name\":\"&&\"", +"图片": "img\":\"&&\"", +"副标题": "qingxidu\":\"&&\"", +"链接": "/api.php/provide/vod_detail?ac=vod_detail&id=+id\":&&,", + +"影片年代": "msg\":\"&&\"", +"影片地区": "vod_area\":\"&&\"", +"影片类型": "type\":\"&&\"", +"状态": "remarks\":\"&&\"", +"导演": "director\":\"&&\"", +"主演": "actor\":\"&&\"", +"简介": "\"info\":\"&&\"", + +"线路数组": "show\":&&,", +"线路标题": "🔞+\"&&\"", +"播放二次截取": "playlist\":&&,[替换:$$$>>接表组组表题#\">>接表组表题#$>>题接#\\#>>接表表题]", +"播放数组": "组&&组", +"播放列表": "表&&表", +"播放标题": "题&&题", +"播放链接": "接&&接", + +"直接播放": "0", +"嗅探词": ".mp4#.m3u8", + +"搜索请求头": "User-Agent$MOBILE_UA", +"搜索url": "/api.php/provide/search_result?video_name={wd}", +"搜索模式": "1", +"搜索二次截取": "search_result\":\\[&&\\]", +"搜索数组": "{&&}", +"搜索标题": "video_name\":\"&&\"", +"搜索图片": "img\":\"&&\"", +"搜索副标题": "\"\":\"&&\"", +"搜索链接": "/api.php/provide/vod_detail?ac=vod_detail&id=+id\":&&,", + +"筛选": "1", +"年份": "1949-2025", +"年份值": "*", +"排序": "为你推荐&最新&评分&最热", +"排序值": "空&new&score&hits" +} \ No newline at end of file diff --git a/js/adult/采集福利.json$1$1 b/js/adult/采集福利.json$1$1 new file mode 100644 index 0000000..9a6a89f --- /dev/null +++ b/js/adult/采集福利.json$1$1 @@ -0,0 +1,378 @@ +globalThis.getRandomItem = function(items) { + return items[Math.random() * items.length | 0]; +} +var rule = { + title: '采集之王[合]', + author: '艾丝沐', + version: '20240706 beta17', + update_info: ``.trim(), + host: '', + homeTid: '', + homeUrl: '/api.php/provide/vod/?ac=detail&t={{rule.homeTid}}', + detailUrl: '/api.php/provide/vod/?ac=detail&ids=fyid', + searchUrl: '/api.php/provide/vod/?wd=**&pg=#TruePage##page=fypage', + classUrl: '/api.php/provide/vod/', + url: '/api.php/provide/vod/?ac=detail&pg=fypage&t=fyfilter', + filter_url: '{{fl.类型}}', + headers: { + 'User-Agent': 'MOBILE_UA' + }, + timeout: 5000, + limit: 20, + search_limit: 10, + searchable: 1, + quickSearch: 0, + filterable: 1, + play_parse: true, + parse_url: '', + search_match: false, + search_pic: true, + 预处理: $js.toString(() => { + function getClasses(item) { + let classes = []; + if (item.class_name && item.class_url) { + if (!/&|电影|电视剧|综艺|动漫[\u4E00-\u9FA5]+/.test(item.class_name)) { + try { + item.class_name = ungzip(item.class_name) + } catch (e) { + log(`不识别的class_name导致gzip解码失败:${e}`) + return classes + } + } + let names = item.class_name.split('&'); + let urls = item.class_url.split('&'); + let cnt = Math.min(names.length, urls.length); + for (let i = 0; i < cnt; i++) { + classes.push({ + 'type_id': urls[i], + 'type_name': names[i] + }); + } + } + return classes + } + if (typeof(batchFetch) === 'function') { + rule.search_limit = 16; + log('当前程序支持批量请求[batchFetch],搜索限制已设置为16'); + } + let _url = rule.params; + log(`传入参数:${_url}`); + if (_url && typeof(_url) === 'string' && /^(http|file)/.test(_url)) { + if (_url.includes('$')) { + let _url_params = _url.split('$'); + _url = _url_params[0]; + rule.search_match = !!(_url_params[1]); + if (_url_params.length > 2) { + rule.search_pic = !!(_url_params[2]); + } + } + let html = request(_url); + let json = JSON.parse(html); + let _classes = []; + rule.filter = {}; + rule.filter_def = {}; + json.forEach(it => { + let _obj = { + type_name: it.name, + type_id: it.url, + parse_url: it.parse_url || '', + searchable: it.searchable !== 0, + api: it.api || '', + cate_exclude: it.cate_exclude || '', + cate_excludes: it.cate_excludes || [], + }; + _classes.push(_obj); + try { + let json1 = []; + if (it.class_name && it.class_url) { + json1 = getClasses(it); + } else { + json1 = JSON.parse(request(urljoin(_obj.type_id, _obj.api || rule.classUrl))).class; + } + if (_obj.cate_excludes && Array.isArray(_obj.cate_excludes) && _obj.cate_excludes.length > 0) { + json1 = json1.filter(cl => !_obj.cate_excludes.includes(cl.type_name)); + } else if (_obj.cate_exclude) { + json1 = json1.filter(cl => !new RegExp(_obj.cate_exclude, 'i').test(cl.type_name)); + } + rule.filter[_obj.type_id] = [{ + "key": "类型", + "name": "类型", + "value": json1.map(i => { + return { + "n": i.type_name, + 'v': i.type_id + } + }) + }]; + if (json1.length > 0) { + rule.filter_def[it.url] = { + "类型": json1[0].type_id + }; + } + } catch (e) { + rule.filter[it.url] = [{ + "key": "类型", + "name": "类型", + "value": [{ + "n": "全部", + "v": "" + }] + }]; + } + }); + rule.classes = _classes; + } + }), + class_parse: $js.toString(() => { + input = rule.classes; + }), + 推荐: $js.toString(() => { + VODS = []; + if (rule.classes) { + let randomClass = getRandomItem(rule.classes); + let _url = urljoin(randomClass.type_id, input); + if (randomClass.api) { + _url = _url.replace('/api.php/provide/vod/', randomClass.api) + } + try { + let html = request(_url, { + timeout: rule.timeout + }); + let json = JSON.parse(html); + VODS = json.list; + VODS.forEach(it => { + it.vod_id = randomClass.type_id + '$' + it.vod_id; + it.vod_remarks = it.vod_remarks + '|' + randomClass.type_name; + }); + } catch (e) {} + } + }), + 一级: $js.toString(() => { + VODS = []; + if (rule.classes) { + let _url = urljoin(MY_CATE, input); + let current_vod = rule.classes.find(item => item.type_id === MY_CATE); + if (current_vod && current_vod.api) { + _url = _url.replace('/api.php/provide/vod/', current_vod.api) + } + let html = request(_url); + let json = JSON.parse(html); + VODS = json.list; + VODS.forEach(it => { + it.vod_id = MY_CATE + '$' + it.vod_id + }); + } + }), + 二级: $js.toString(() => { + VOD = {}; + if (orId === 'update_info') { + VOD = { + vod_content: rule.update_info.trim(), + vod_name: '更新日志', + type_name: '更新日志', + vod_pic: 'https://resource-cdn.tuxiaobei.com/video/FtWhs2mewX_7nEuE51_k6zvg6awl.png', + vod_remarks: `版本:${rule.version}`, + vod_play_from: '艾丝沐在线', + vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php', + }; + } else { + if (rule.classes) { + let _url = urljoin(fyclass, input); + let current_vod = rule.classes.find(item => item.type_id === fyclass); + if (current_vod && current_vod.api) { + _url = _url.replace('/api.php/provide/vod/', current_vod.api) + } + let html = request(_url); + let json = JSON.parse(html); + let data = json.list; + VOD = data[0]; + if (current_vod && current_vod.type_name) { + VOD.vod_play_from = VOD.vod_play_from.split('$$$').map(it => current_vod.type_name + '|' + it).join('$$$') + } + } + } + }), + 搜索: $js.toString(() => { + VODS = []; + if (rule.classes) { + let canSearch = rule.classes.filter(it => it.searchable); + let page = Number(MY_PAGE); + page = (MY_PAGE - 1) % Math.ceil(canSearch.length / rule.search_limit) + 1; + let truePage = Math.ceil(MY_PAGE / Math.ceil(canSearch.length / rule.search_limit)); + if (rule.search_limit) { + let start = (page - 1) * rule.search_limit; + let end = page * rule.search_limit; + let t1 = new Date().getTime(); + let searchMode = typeof(batchFetch) === 'function' ? '批量' : '单个'; + log('start:' + start); + log('end:' + end); + log('搜索模式:' + searchMode); + log('精准搜索:' + rule.search_match); + log('强制获取图片:' + rule.search_pic); + if (start < canSearch.length) { + let search_classes = canSearch.slice(start, end); + let urls = []; + search_classes.forEach(it => { + let _url = urljoin(it.type_id, input); + if (it.api) { + _url = _url.replace('/api.php/provide/vod/', it.api) + } + _url = _url.replace("#TruePage#", "" + truePage); + urls.push(_url); + }); + let results_list = []; + let results = []; + if (typeof(batchFetch) === 'function') { + let reqUrls = urls.map(it => { + return { + url: it, + options: { + timeout: rule.timeout + } + } + }); + let rets = batchFetch(reqUrls); + let detailUrls = []; + let detailUrlCount = 0; + rets.forEach((ret, idx) => { + let it = search_classes[idx]; + if (ret) { + try { + let json = JSON.parse(ret); + let data = json.list; + data.forEach(i => { + i.site_name = it.type_name; + i.vod_id = it.type_id + '$' + i.vod_id; + i.vod_remarks = i.vod_remarks + '|' + it.type_name; + }); + if (rule.search_match) { + data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name)) + } + if (data.length > 0) { + if (rule.search_pic && !data[0].vod_pic) { + log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`); + let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(','); + detailUrls.push(detailUrl); + results_list.push({ + data: data, + has_pic: false, + detailUrlCount: detailUrlCount + }); + detailUrlCount++; + } else { + results_list.push({ + data: data, + has_pic: true + }); + } + } + } catch (e) { + log(`请求:${it.type_id}发生错误:${e.message}`) + } + } + }); + let reqUrls2 = detailUrls.map(it => { + return { + url: it, + options: { + timeout: rule.timeout + } + } + }); + let rets2 = reqUrls2.length > 0 ? batchFetch(reqUrls2) : []; + for (let k = 0; k < results_list.length; k++) { + let result_data = results_list[k].data; + if (!results_list[k].has_pic) { + try { + let detailJson = JSON.parse(rets2[results_list[k].detailUrlCount]); + log('二级数据列表元素数:' + detailJson.list.length); + result_data.forEach((d, _seq) => { + let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]); + if (detailVodPic) { + Object.assign(d, { + vod_pic: detailVodPic.vod_pic + }); + } + }); + } catch (e) { + log(`强制获取网站${result_data[0].site_name}的搜索图片失败:${e.message}`); + } + } + results = results.concat(result_data); + } + } else { + urls.forEach((_url, idx) => { + let it = search_classes[idx]; + try { + let html = request(_url); + let json = JSON.parse(html); + let data = json.list; + data.forEach(i => { + i.vod_id = it.type_id + '$' + i.vod_id; + i.vod_remarks = i.vod_remarks + '|' + it.type_name; + }); + if (rule.search_match) { + data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name)) + } + if (data.length > 0) { + if (rule.search_pic && !data[0].vod_pic) { + log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`); + let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(','); + try { + let detailJson = JSON.parse(request(detailUrl)); + log('二级数据列表元素数:' + detailJson.list.length); + data.forEach((d, _seq) => { + let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]); + if (detailVodPic) { + Object.assign(d, { + vod_pic: detailVodPic.vod_pic + }); + } + }); + } catch (e) { + log(`强制获取网站${it.type_id}的搜索图片失败:${e.message}`); + } + } + results = results.concat(data); + } + results = results.concat(data); + } catch (e) { + log(`请求:${it.type_id}发生错误:${e.message}`) + } + }); + } + VODS = results; + let t2 = new Date().getTime(); + log(`${searchMode}搜索:${urls.length}个站耗时:${(Number(t2) - Number(t1))}ms`) + } + } + } + }), + lazy: $js.toString(() => { + let parse_url = ''; + if (flag && flag.includes('|')) { + let type_name = flag.split('|')[0]; + let current_vod = rule.classes.find(item => item.type_name === type_name); + if (current_vod && current_vod.parse_url) { + parse_url = current_vod.parse_url + } + } + if (/\.(m3u8|mp4)/.test(input)) { + input = { + parse: 0, + url: input + } + } else { + if (parse_url.startsWith('json:')) { + let purl = parse_url.replace('json:', '') + input; + let html = request(purl); + input = { + parse: 0, + url: JSON.parse(html).url + } + } else { + input = parse_url + input; + } + } + }), +} \ No newline at end of file diff --git a/py/adult/javxbb.py b/py/adult/javxbb.py new file mode 100644 index 0000000..705519a --- /dev/null +++ b/py/adult/javxbb.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +#author 🍑 +import json +import re +import os +import sys +import requests +from requests.exceptions import RequestException +try: + from pyquery import PyQuery as pq +except Exception: + pq = None +from base.spider import Spider + +class Spider(Spider): + name = 'Javbobo' + host = 'https://javbobo.com' + def init(self, extend=""): + try: + self.extend = json.loads(extend) if extend else {} + except Exception: + self.extend = {} + self.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Referer': f'{self.host}/', + 'Origin': self.host, + 'Connection': 'keep-alive', + } + self.session = requests.Session() + self.session.headers.update(self.headers) + def getName(self): + return self.name + def isVideoFormat(self, url): + return any(ext in (url or '') for ext in ['.m3u8', '.mp4', '.ts']) + def manualVideoCheck(self): + return False + def destroy(self): + pass + def homeContent(self, filter): + result = {} + try: + cateManual = [ + {'type_name': '日本有碼', 'type_id': '47'}, + {'type_name': '日本無碼', 'type_id': '48'}, + {'type_name': '國產AV', 'type_id': '49'}, + {'type_name': '網紅主播', 'type_id': '50'}, + ] + result['class'] = cateManual + result['filters'] = {} + except Exception: + pass + return result + def homeVideoContent(self): + return self.categoryContent('', '1', False, {}) + def categoryContent(self, tid, pg, filter, extend): + pg = str(pg) + result = {'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999, 'list': []} + try: + url = self.host + if tid: + if str(tid).startswith('http'): + url = str(tid) + if pg != '1': url = f"{url}{'&' if '?' in url else '?'}page={pg}" + elif str(tid).startswith('/'): + url = f"{self.host}{tid}" + if pg != '1': url = f"{url}{'&' if '?' in url else '?'}page={pg}" + else: + url = f"{self.host}/vod/index.html?type_id={tid}" + if pg != '1': url = f"{self.host}/vod/index.html?page={pg}&type_id={tid}" + resp = self.session.get(url, timeout=30) + resp.raise_for_status() + if pq is None: raise RuntimeError('PyQuery 未安装,无法解析列表页面') + doc = pq(resp.text) + def _parse_list(doc): + vlist = [] + seen = set() + for a in doc('a[href*="/vod/player.html"]').items(): + href = a.attr('href') or '' + if not href: continue + full = href if href.startswith('http') else f"{self.host}{href}" + m = re.search(r'[?&]id=(\d+)', full) + if not m: continue + vid = m.group(1) + if vid in seen: continue + seen.add(vid) + img_el = a('img') + title = img_el.attr('alt') or a.attr('title') or (a.text() or '').strip() + if not title: + li = a.parents('li').eq(0) + title = li.find('h1,h2,h3').text().strip() if li else '' + if not title: title = f"视频{vid}" + img = img_el.attr('src') or img_el.attr('data-src') or '' + if img and not img.startswith('http'): img = f"{self.host}{img}" + vlist.append({ + 'vod_id': full, 'vod_name': title, 'vod_pic': img, 'vod_remarks': '', + 'style': {'ratio': 1.33, 'type': 'rect'} + }) + if len(vlist) >= 90: break + return vlist + result['list'] = _parse_list(doc) + page_numbers = [] + for a in doc('a[href*="/vod/index.html?page="]').items(): + t = (a.text() or '').strip() + if t.isdigit(): page_numbers.append(int(t)) + if page_numbers: result['pagecount'] = max(page_numbers) + except Exception: + result['list'] = [] + return result + def detailContent(self, ids): + try: + url = ids[0] if isinstance(ids, list) else str(ids) + if not url: return {'list': []} + if not url.startswith('http'): url = f"{self.host}/vod/player.html?id={url}" + resp = self.session.get(url, timeout=30) + resp.raise_for_status() + html = resp.text + if pq is None: raise RuntimeError('PyQuery 未安装,无法解析详情页面') + doc = pq(html) + title = doc('meta[property="og:title"]').attr('content') or doc('h1').text().strip() or 'Javbobo 视频' + vod_pic = doc('meta[property="og:image"]').attr('content') or '' + if not vod_pic: + img_el = doc('img').eq(0) + vod_pic = img_el.attr('src') or img_el.attr('data-src') or '' + if vod_pic and not vod_pic.startswith('http'): vod_pic = f"{self.host}{vod_pic}" + line_id = None + m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html) + if m: line_id = m.group(1) + if not line_id: + m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html) + if m: line_id = m.group(1) + play_id = line_id or url + vod = { + 'vod_name': title, 'vod_pic': vod_pic, 'vod_content': '', + 'vod_play_from': 'Javbobo', 'vod_play_url': f'正片${play_id}' + } + return {'list': [vod]} + except Exception: + return {'list': []} + def searchContent(self, key, quick, pg="1"): + try: + params = {'wd': key} + url = f"{self.host}/index.html" + resp = self.session.get(url, params=params, timeout=30) + resp.raise_for_status() + if pq is None: raise RuntimeError('PyQuery 未安装,无法解析搜索页面') + doc = pq(resp.text) + vlist = [] + seen = set() + for a in doc('a[href*="/vod/player.html"]').items(): + href = a.attr('href') or '' + if not href: continue + full = href if href.startswith('http') else f"{self.host}{href}" + m = re.search(r'[?&]id=(\d+)', full) + if not m: continue + vid = m.group(1) + if vid in seen: continue + seen.add(vid) + img_el = a('img') + title = img_el.attr('alt') or a.attr('title') or (a.text() or '').strip() + img = img_el.attr('src') or img_el.attr('data-src') or '' + if img and not img.startswith('http'): img = f"{self.host}{img}" + vlist.append({ + 'vod_id': full, 'vod_name': title or f'视频{vid}', 'vod_pic': img, + 'vod_remarks': '', 'style': {'ratio': 1.33, 'type': 'rect'} + }) + if len(vlist) >= 60: break + return {'list': vlist, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999} + except Exception: + return {'list': []} + def playerContent(self, flag, id, vipFlags): + try: + line_id = None + sid = str(id or '') + if re.fullmatch(r'\d+', sid): + line_id = sid + elif sid.startswith('http'): + if self.isVideoFormat(sid): + headers = {'User-Agent': self.headers['User-Agent'], 'Referer': f'{self.host}/'} + return {'parse': 0, 'url': sid, 'header': headers} + html = self.session.get(sid, timeout=30).text + m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html) + if m: line_id = m.group(1) + if not line_id: + m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html) + if m: line_id = m.group(1) + else: + if sid.startswith('/'): page_url = f"{self.host}{sid}" + else: page_url = f"{self.host}/vod/player.html?id={sid}" + html = self.session.get(page_url, timeout=30).text + m = re.search(r"lineId\s*=\s*Number\('?(\d+)'?\)", html) + if m: line_id = m.group(1) + if not line_id: + m = re.search(r"var\s+Iyplayer\s*=\s*\{[^}]*id:(\d+)", html) + if m: line_id = m.group(1) + if not line_id: raise ValueError('未能获取到播放线路ID(lineId)') + api = f"{self.host}/openapi/playline/{line_id}" + r = self.session.get(api, timeout=30) + txt = r.text.strip() + j = None + try: j = r.json() + except Exception: j = None + if isinstance(j, str): + try: j = json.loads(j) + except Exception: j = None + if not isinstance(j, dict): + try: j = json.loads(txt) + except Exception: j = {} + m3u8_url = '' + if isinstance(j, dict): m3u8_url = j.get('info', {}).get('file') or j.get('file') or '' + headers = {'User-Agent': self.headers['User-Agent'], 'Referer': f'{self.host}/'} + return {'parse': 0, 'url': m3u8_url, 'header': headers} + except Exception: + return {'parse': 0, 'url': '', 'header': {}} \ No newline at end of file