增加二级分类及筛选功能
This commit is contained in:
40
js/README.md
40
js/README.md
@@ -19,10 +19,50 @@
|
||||
|
||||
```
|
||||
|
||||
# 脚本运行说明
|
||||
|
||||
1. 执行每个方法都会为 `webSite` 进行赋值
|
||||
2. 流程图
|
||||
|
||||
```mermaid
|
||||
|
||||
graph TD
|
||||
|
||||
A[开始] --> A1[uz 调用 getClassList 获取一级分类] -->|返回数据 rep: RepVideoClassList| B[判断 rep.data 列表内 VideoClass 的 hasSubclass 是否为 true]
|
||||
|
||||
B -->|是,存在二级分类或者筛选列表| C[调用 getSubclassList 获取二级分类或筛选列表]
|
||||
|
||||
B -->|否,不存在二级分类| D[调用 getVideoList 获取视频列表]
|
||||
|
||||
|
||||
C --> C1[调用 getSubclassVideoList 获取二级分类视频列表或者筛选视频列表] -->|点击单个视频| E
|
||||
|
||||
|
||||
|
||||
E[调用 getVideoDetail 获取视频详情]
|
||||
|
||||
D -->|点击单个视频| E
|
||||
|
||||
E -->|点击某一集| F[调用 getVideoPlayUrl 获取播放链接]
|
||||
|
||||
F --> G[结束]
|
||||
|
||||
S[搜索] -->S1[调用 searchVideo 返回视频列表] -->|点击单个视频| E
|
||||
|
||||
```
|
||||
|
||||
# 修改记录
|
||||
|
||||
### v1.4.00
|
||||
|
||||
1. 增加二级分类和筛选列表功能
|
||||
|
||||
### v1.3.00
|
||||
|
||||
1. 去掉 `cat.js`, 更改为 `cheerio` `Crypto` `Encrypt` `parse`
|
||||
2. `VideoDetail` 新增 `quarkUrl` 支持夸克网盘
|
||||
3. `RepVideoPlayUrl` 新增 `headers` 支持设置播放 `header`
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
/**
|
||||
* 筛选标签
|
||||
*/
|
||||
class FilterLabel {
|
||||
constructor() {
|
||||
// 筛选名称
|
||||
this.name = "";
|
||||
// 标识参数
|
||||
this.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选标题
|
||||
*/
|
||||
class FilterTitle {
|
||||
constructor() {
|
||||
// 筛选标题
|
||||
this.name = "";
|
||||
/**
|
||||
* 筛选标签列表
|
||||
* @type {FilterLabel[]}
|
||||
*/
|
||||
this.list = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频分类
|
||||
*/
|
||||
@@ -7,6 +34,30 @@ class VideoClass {
|
||||
this.type_id = "";
|
||||
// 分类名称
|
||||
this.type_name = "";
|
||||
|
||||
/**
|
||||
* 是否存在 筛选列表、子分类。 存在会调用 getSubclassList
|
||||
*/
|
||||
this.hasSubclass = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频二级分类,二级分类可以是 分类,也可以是筛选,都有值优先取筛选
|
||||
*/
|
||||
class VideoSubclass {
|
||||
constructor() {
|
||||
/**
|
||||
* 子分类
|
||||
* @type {VideoClass[]}
|
||||
*/
|
||||
this.class = [];
|
||||
/**
|
||||
* 筛选列表
|
||||
* 请求二级分类列表 getSubclassList 时返回该数据或者 data,
|
||||
* @type {FilterTitle[]}
|
||||
*/
|
||||
this.filter = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +117,20 @@ class RepVideoClassList {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回二级分类列表(包括筛选列表)
|
||||
*/
|
||||
class RepVideoSubclassList {
|
||||
constructor() {
|
||||
/**
|
||||
* 二级分类数据
|
||||
* @type {VideoSubclass}
|
||||
*/
|
||||
this.data = new VideoSubclass();
|
||||
this.error = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回视频列表
|
||||
*/
|
||||
@@ -74,7 +139,7 @@ class RepVideoList {
|
||||
/**
|
||||
* @type {VideoDetail[]}
|
||||
*/
|
||||
this.data = null;
|
||||
this.data = [];
|
||||
this.error = "";
|
||||
this.total = 0;
|
||||
}
|
||||
@@ -120,10 +185,36 @@ class UZArgs {
|
||||
this.searchWord = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getSubclassVideoList 方法传入的参数
|
||||
*/
|
||||
class UZSubclassVideoListArgs extends UZArgs {
|
||||
constructor() {
|
||||
/**
|
||||
* 主分类ID 即脚本返回的 @type {RepVideoClassList}.data[0].type_id
|
||||
*/
|
||||
this.mainClassId = "";
|
||||
|
||||
/**
|
||||
* 二级分类ID 即脚本返回的 @type {RepVideoSubclassList}.data.class.type_id
|
||||
*/
|
||||
this.subclassId = "";
|
||||
|
||||
/**
|
||||
* 筛选标签,按返回的顺序传入 即脚本返回的 @type {RepVideoSubclassList}.data.filter.
|
||||
* @type {FilterLabel[]}
|
||||
*/
|
||||
this.filter = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 脚本基类
|
||||
*/
|
||||
class WebApiBase {
|
||||
// 网站主页
|
||||
webSite = "";
|
||||
/**
|
||||
* 异步获取分类列表的方法。
|
||||
* @param {UZArgs} args
|
||||
@@ -133,6 +224,15 @@ class WebApiBase {
|
||||
return JSON.stringify(new RepVideoClassList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级分类列表筛选列表的方法。
|
||||
* @param {UZArgs} args
|
||||
* @returns {@Promise<JSON.stringify(new RepVideoSubclassList())>}
|
||||
*/
|
||||
async getSubclassList(args) {
|
||||
return JSON.stringify(new RepVideoSubclassList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类视频列表
|
||||
* @param {UZArgs} args
|
||||
@@ -141,6 +241,15 @@ class WebApiBase {
|
||||
async getVideoList(args) {
|
||||
return JSON.stringify(new RepVideoList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级分类视频列表 或 筛选视频列表
|
||||
* @param {UZSubclassVideoListArgs} args
|
||||
* @returns {@Promise<JSON.stringify(new RepVideoList())>}
|
||||
*/
|
||||
async getSubclassVideoList(args) {
|
||||
return JSON.stringify(new RepVideoList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
|
||||
342
js/spider/keke20240712.js
Normal file
342
js/spider/keke20240712.js
Normal file
@@ -0,0 +1,342 @@
|
||||
// ignore
|
||||
import { WebApiBase, VideoClass } from "../core/uzCode.js";
|
||||
import { parse } from "node-html-parser";
|
||||
// ignore
|
||||
|
||||
// 类名要特殊
|
||||
class Keke20240712 extends WebApiBase {
|
||||
webSite = "https://www.keke12.com:51111";
|
||||
/**
|
||||
* 异步获取分类列表的方法。
|
||||
* @param {UZArgs} args
|
||||
* @returns {Promise<RepVideoClassList>}
|
||||
*/
|
||||
async getClassList(args) {
|
||||
let webUrl = args.url;
|
||||
var backData = new RepVideoClassList();
|
||||
backData.data = [];
|
||||
try {
|
||||
const pro = await req(webUrl);
|
||||
backData.error = pro.error;
|
||||
let proData = pro.data;
|
||||
if (proData) {
|
||||
var document = parse(proData);
|
||||
var ulList = document.querySelectorAll("div.main > ul") ?? [];
|
||||
|
||||
if (ulList.length >= 1) {
|
||||
var li = ulList[1].querySelectorAll("li") ?? [];
|
||||
for (let i = 0; i < li.length; i++) {
|
||||
const element = li[i];
|
||||
const title = element.querySelector(".menu-item-label").text;
|
||||
const path = element.querySelector("a").attributes["href"];
|
||||
const id = UZUtils.getStrByRegexDefault(/\/(\d+)\.html/, path);
|
||||
var videoClass = new VideoClass();
|
||||
videoClass.hasSubclass = true;
|
||||
videoClass.type_id = id;
|
||||
videoClass.type_name = title;
|
||||
backData.data.push(videoClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
backData.error = "获取分类失败~";
|
||||
}
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
|
||||
async getSubclassList(args) {
|
||||
var backData = new RepVideoSubclassList();
|
||||
backData.data = new VideoSubclass();
|
||||
const id = args.url;
|
||||
try {
|
||||
var url =
|
||||
UZUtils.removeTrailingSlash(this.webSite) +
|
||||
"/show/" +
|
||||
id +
|
||||
"------1.html";
|
||||
const pro = await req(url);
|
||||
backData.error = pro.error;
|
||||
let proData = pro.data;
|
||||
if (proData) {
|
||||
var document = parse(proData);
|
||||
var filterTitleList = document.querySelectorAll("div.filter-row") ?? [];
|
||||
for (let i = 0; i < filterTitleList.length; i++) {
|
||||
const element = filterTitleList[i];
|
||||
const title = element.querySelector(".filter-row-side > strong").text;
|
||||
const items = element.querySelectorAll(".filter-item") ?? [];
|
||||
// 2-惊悚-中国香港-英语-2022-1-1
|
||||
// 分类-类型-地区-语言-年代-排序-页码
|
||||
var filterTitle = new FilterTitle();
|
||||
filterTitle.name = title.replace(":", "");
|
||||
filterTitle.list = [];
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
const item = items[j];
|
||||
const name = item.text;
|
||||
const path = item.attributes["href"] ?? "";
|
||||
const regex = /\/show\/(.*?)\.html/;
|
||||
const match = path.match(regex);
|
||||
const parsStr = match ? match[1] : null;
|
||||
if (parsStr) {
|
||||
const parList = parsStr.split("-");
|
||||
const id = parList[i + 1];
|
||||
var filterLab = new FilterLabel();
|
||||
filterLab.name = name;
|
||||
filterLab.id = id;
|
||||
filterTitle.list.push(filterLab);
|
||||
}
|
||||
}
|
||||
|
||||
backData.data.filter.push(filterTitle);
|
||||
}
|
||||
if (id === 6 || id === "6") {
|
||||
// 短剧
|
||||
if (backData.data.filter.length > 0) {
|
||||
const list = backData.data.filter[0].list;
|
||||
var classList = [];
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
var subclass = new VideoClass();
|
||||
subclass.type_id = element.id;
|
||||
subclass.type_name = element.name;
|
||||
classList.push(subclass);
|
||||
}
|
||||
backData.data.filter = [];
|
||||
backData.data.class = classList;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
backData.error = "获取分类失败~ " + error;
|
||||
}
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
/**
|
||||
* 获取二级分类视频列表 或 筛选视频列表
|
||||
* @param {UZSubclassVideoListArgs} args
|
||||
* @returns {@Promise<JSON.stringify(new RepVideoList())>}
|
||||
*/
|
||||
async getSubclassVideoList(args) {
|
||||
var backData = new RepVideoList();
|
||||
backData.data = [];
|
||||
try {
|
||||
var pList = [args.mainClassId];
|
||||
if (args.filter.length > 0) {
|
||||
// 筛选
|
||||
for (let index = 0; index < args.filter.length; index++) {
|
||||
const element = args.filter[index];
|
||||
pList.push(element.id);
|
||||
}
|
||||
} else {
|
||||
pList.push(args.subclassId);
|
||||
for (let index = 0; index < 4; index++) {
|
||||
pList.push("");
|
||||
}
|
||||
}
|
||||
pList.push(args.page);
|
||||
var path = pList.join("-");
|
||||
const url =
|
||||
UZUtils.removeTrailingSlash(this.webSite) + "/show/" + path + ".html";
|
||||
|
||||
const pro = await req(url);
|
||||
backData.error = pro.error;
|
||||
let proData = pro.data;
|
||||
if (proData) {
|
||||
var document = parse(proData);
|
||||
var allVideo = document.querySelectorAll("div.module-item") ?? [];
|
||||
var videos = [];
|
||||
for (let index = 0; index < allVideo.length; index++) {
|
||||
const element = allVideo[index];
|
||||
var vodUrl = element.querySelector("a")?.attributes["href"] ?? "";
|
||||
var avaImg =
|
||||
document.querySelector("img.user-avatar-img")?.attributes["src"] ??
|
||||
"";
|
||||
var path =
|
||||
element.querySelector("img")?.attributes["data-original"] ?? "";
|
||||
var vodPic = UZUtils.getHostFromURL(avaImg) + path;
|
||||
var vodName = element.querySelector("img")?.attributes["title"] ?? "";
|
||||
var vod_remarks =
|
||||
element.querySelector("div.v-item-bottom > span")?.text ??
|
||||
element.querySelector("div.v-item-top-left > span")?.text;
|
||||
if (vodUrl && vodPic && vodName) {
|
||||
var video = new VideoDetail();
|
||||
video.vod_id = vodUrl;
|
||||
video.vod_pic = vodPic;
|
||||
video.vod_name = vodName;
|
||||
video.vod_remarks = vod_remarks;
|
||||
videos.push(video);
|
||||
}
|
||||
}
|
||||
backData.data = videos;
|
||||
}
|
||||
} catch (error) {
|
||||
backData.error = "获取视频列表失败~ " + error;
|
||||
}
|
||||
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类视频列表
|
||||
* @param {UZArgs} args
|
||||
* @returns {Promise<RepVideoList>}
|
||||
*/
|
||||
async getVideoList(args) {
|
||||
var backData = new RepVideoList();
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
* @param {UZArgs} args
|
||||
* @returns {Promise<RepVideoDetail>}
|
||||
*/
|
||||
async getVideoDetail(args) {
|
||||
var backData = new RepVideoDetail();
|
||||
try {
|
||||
var webUrl = UZUtils.removeTrailingSlash(this.webSite) + args.url;
|
||||
let pro = await req(webUrl, null);
|
||||
backData.error = pro.error;
|
||||
let proData = pro.data;
|
||||
if (proData) {
|
||||
var document = parse(proData);
|
||||
|
||||
var detList = document.querySelectorAll("div.detail-info-row") ?? [];
|
||||
var vod_year = "";
|
||||
var vod_director = "";
|
||||
var vod_actor = "";
|
||||
|
||||
for (let index = 0; index < detList.length; index++) {
|
||||
const element = detList[index];
|
||||
var title =
|
||||
element.querySelector("div.detail-info-row-side")?.text ?? "";
|
||||
var contentE = element.querySelector("div.detail-info-row-main");
|
||||
if (title.includes("首映")) {
|
||||
vod_year = contentE.text ?? "";
|
||||
} else if (title.includes("导演")) {
|
||||
vod_director = contentE?.querySelector("a")?.text ?? "";
|
||||
} else if (title.includes("演员")) {
|
||||
contentE.querySelectorAll("a").forEach((element) => {
|
||||
vod_actor += element.text + "、";
|
||||
});
|
||||
}
|
||||
}
|
||||
var vod_content =
|
||||
document.querySelector("div.detail-desc > p").text?.trim() ?? "";
|
||||
|
||||
var fromListE =
|
||||
document.querySelectorAll("span.source-item-label") ?? [];
|
||||
var fromListStr = [];
|
||||
for (let index = 0; index < fromListE.length; index++) {
|
||||
fromListStr.push(fromListE[index].text);
|
||||
}
|
||||
var vod_play_from = fromListStr.join("$$$");
|
||||
|
||||
var playListE = document.querySelectorAll("div.episode-list") ?? [];
|
||||
var vod_play_url = "";
|
||||
for (let index = 0; index < playListE.length; index++) {
|
||||
const epList = playListE[index].querySelectorAll("a") ?? [];
|
||||
|
||||
for (let index = 0; index < epList.length; index++) {
|
||||
const element = epList[index];
|
||||
vod_play_url += element.text;
|
||||
vod_play_url += "$";
|
||||
vod_play_url += element.attributes["href"];
|
||||
vod_play_url += "#";
|
||||
}
|
||||
vod_play_url += "$$$";
|
||||
}
|
||||
|
||||
let detModel = new VideoDetail();
|
||||
detModel.vod_year = vod_year;
|
||||
detModel.vod_director = vod_director;
|
||||
detModel.vod_actor = vod_actor;
|
||||
detModel.vod_content = vod_content;
|
||||
detModel.vod_play_url = vod_play_url;
|
||||
detModel.vod_play_from = vod_play_from;
|
||||
detModel.vod_id = args.url;
|
||||
backData.data = detModel;
|
||||
}
|
||||
} catch (error) {
|
||||
backData.error = "获取视频详情失败 ~ " + error;
|
||||
}
|
||||
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频的播放地址
|
||||
* @param {UZArgs} args
|
||||
* @returns {Promise<RepVideoPlayUrl>}
|
||||
*/
|
||||
async getVideoPlayUrl(args) {
|
||||
var backData = new RepVideoPlayUrl();
|
||||
backData.error = "获取播放链接失败~";
|
||||
try {
|
||||
const pro = await req(
|
||||
UZUtils.removeTrailingSlash(this.webSite) + args.url
|
||||
);
|
||||
backData.error = pro.error;
|
||||
let proData = pro.data;
|
||||
const regex = /playSource\s*=\s*{[^}]*src:\s*"([^"]*)"/;
|
||||
const match = proData.match(regex);
|
||||
const result = match ? match[1] : "";
|
||||
backData.data = result;
|
||||
} catch (error) {
|
||||
backData.error = "获取视频播放地址失败~ " + error;
|
||||
}
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索视频
|
||||
* @param {UZArgs} args
|
||||
* @returns {Promise<RepVideoList>}
|
||||
*/
|
||||
async searchVideo(args) {
|
||||
var backData = new RepVideoList();
|
||||
backData.data = [];
|
||||
try {
|
||||
const rep = await req(
|
||||
UZUtils.removeTrailingSlash(this.webSite) +
|
||||
"/search?k=" +
|
||||
args.searchWord +
|
||||
"&page=" +
|
||||
args.page
|
||||
);
|
||||
backData.error = rep.error;
|
||||
let repData = rep.data;
|
||||
var document = parse(repData);
|
||||
var count = document.querySelectorAll("span.highlight-text") ?? [];
|
||||
if (count.length > 1) {
|
||||
backData.total = count[1].text.trim();
|
||||
}
|
||||
const allVideoE = document.querySelectorAll("a.search-result-item") ?? [];
|
||||
|
||||
for (let index = 0; index < allVideoE.length; index++) {
|
||||
const element = allVideoE[index];
|
||||
var video = new VideoDetail();
|
||||
video.vod_id = element.attributes["href"];
|
||||
|
||||
var avaImg =
|
||||
document.querySelector("img.user-avatar-img")?.attributes["src"] ??
|
||||
"";
|
||||
var path =
|
||||
element.querySelector("img")?.attributes["data-original"] ?? "";
|
||||
var vodPic = UZUtils.getHostFromURL(avaImg) + path;
|
||||
video.vod_pic = vodPic;
|
||||
|
||||
video.vod_name = element.querySelector("div.title")?.text ?? "";
|
||||
video.vod_remarks =
|
||||
element.querySelector("div.search-result-item-header > div")?.text ??
|
||||
"";
|
||||
|
||||
backData.data.push(video);
|
||||
}
|
||||
} catch (error) {
|
||||
backData.error = "搜索视频失败 ~ " + error;
|
||||
}
|
||||
return JSON.stringify(backData);
|
||||
}
|
||||
}
|
||||
// json 中 instance 的值,这个名称一定要特殊
|
||||
var keke20240712 = new Keke20240712();
|
||||
@@ -5,5 +5,12 @@
|
||||
"instance": "changZhang20240614",
|
||||
"webSite": "https://www.czzy77.com",
|
||||
"remark": "部分视频不会解密,不支持搜索"
|
||||
},
|
||||
{
|
||||
"name": "可可影视",
|
||||
"api": "https://mirror.ghproxy.com/https://raw.githubusercontent.com/YYDS678/uzVideo/main/js/spider/keke20240712.js",
|
||||
"instance": "keke20240712",
|
||||
"webSite": "https://www.keke12.com:51111",
|
||||
"remark": ""
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user