feat: improve handling of search results
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"build": "tsc && vite build"
|
"build": "tsc && vite build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
"preact": "10.25.4"
|
"preact": "10.25.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.5
|
||||||
|
version: 22.10.5
|
||||||
preact:
|
preact:
|
||||||
specifier: 10.25.4
|
specifier: 10.25.4
|
||||||
version: 10.25.4
|
version: 10.25.4
|
||||||
@@ -413,6 +416,9 @@ packages:
|
|||||||
'@types/estree@1.0.6':
|
'@types/estree@1.0.6':
|
||||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||||
|
|
||||||
|
'@types/node@22.10.5':
|
||||||
|
resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==}
|
||||||
|
|
||||||
acorn-walk@8.3.4:
|
acorn-walk@8.3.4:
|
||||||
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
|
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
@@ -659,6 +665,9 @@ packages:
|
|||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
undici-types@6.20.0:
|
||||||
|
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||||
|
|
||||||
update-browserslist-db@1.1.1:
|
update-browserslist-db@1.1.1:
|
||||||
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1051,6 +1060,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.6': {}
|
'@types/estree@1.0.6': {}
|
||||||
|
|
||||||
|
'@types/node@22.10.5':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.20.0
|
||||||
|
|
||||||
acorn-walk@8.3.4:
|
acorn-walk@8.3.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
@@ -1294,6 +1307,8 @@ snapshots:
|
|||||||
|
|
||||||
typescript@5.7.2: {}
|
typescript@5.7.2: {}
|
||||||
|
|
||||||
|
undici-types@6.20.0: {}
|
||||||
|
|
||||||
update-browserslist-db@1.1.1(browserslist@4.24.3):
|
update-browserslist-db@1.1.1(browserslist@4.24.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.24.3
|
browserslist: 4.24.3
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ const cleanUrl = (url: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getLibMirror = async () => {
|
const getLibMirror = async () => {
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const user = "javlibcom";
|
const user = "javlibcom";
|
||||||
const res = await fetch(`https://api.github.com/users/${user}`);
|
const res = await fetch(`https://api.github.com/users/${user}`);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const SiteBtn = ({ siteItem, CODE, multipleNavi, hiddenError }: Props) => {
|
|||||||
|
|
||||||
const multipleFlag = multipleNavi && fetchRes?.multipleRes;
|
const multipleFlag = multipleNavi && fetchRes?.multipleRes;
|
||||||
const tag = multipleFlag ? "多结果" : fetchRes?.tag;
|
const tag = multipleFlag ? "多结果" : fetchRes?.tag;
|
||||||
const resultLink = (multipleFlag ? fetchRes.multipResLink : fetchRes?.targetLink) ?? originLink;
|
const resultLink = multipleFlag ? originLink : fetchRes?.resultLink;
|
||||||
const colorClass = fetchRes?.isSuccess ? "jop-button_green " : "jop-button_red ";
|
const colorClass = fetchRes?.isSuccess ? "jop-button_green " : "jop-button_red ";
|
||||||
|
|
||||||
if (hiddenError && !fetchRes?.isSuccess) {
|
if (hiddenError && !fetchRes?.isSuccess) {
|
||||||
@@ -38,7 +38,7 @@ const SiteBtn = ({ siteItem, CODE, multipleNavi, hiddenError }: Props) => {
|
|||||||
<a
|
<a
|
||||||
className={"jop-button " + (loading ? " " : colorClass)}
|
className={"jop-button " + (loading ? " " : colorClass)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={resultLink === "" ? originLink : resultLink}
|
href={!resultLink ? originLink : resultLink}
|
||||||
>
|
>
|
||||||
{tag && <div className="jop-button_label">{tag}</div>}
|
{tag && <div className="jop-button_label">{tag}</div>}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ export const gmPost = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isCaseInsensitiveEqual = (str1: string, str2: string) => {
|
export const isCaseInsensitiveEqual = (str1?: any, str2?: any) => {
|
||||||
|
if (!str1 || !str2) return false;
|
||||||
return str1.toLowerCase() === str2.toLowerCase();
|
return str1.toLowerCase() === str2.toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ export const getCode = (libItem: LibItem): string => {
|
|||||||
|
|
||||||
export const regEnum = {
|
export const regEnum = {
|
||||||
subtitle: /(中文|字幕|subtitle)/,
|
subtitle: /(中文|字幕|subtitle)/,
|
||||||
leakage: /(无码|無碼|泄漏|Uncensored)/,
|
leakage: /(无码|無碼|泄漏|泄露|Uncensored)/,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tagsQuery = ({
|
export const tagsQuery = ({
|
||||||
|
|||||||
@@ -46,15 +46,17 @@ export interface SiteItem_get extends SiteItemBase {
|
|||||||
|
|
||||||
export interface SiteItem_parser extends SiteItemBase {
|
export interface SiteItem_parser extends SiteItemBase {
|
||||||
fetchType: "parser";
|
fetchType: "parser";
|
||||||
|
/** 严格匹配,会检查搜索结果的 code */
|
||||||
|
strictParser?: true;
|
||||||
domQuery: DomQuery_parser;
|
domQuery: DomQuery_parser;
|
||||||
}
|
}
|
||||||
export interface SiteItem_post extends SiteItemBase {
|
// export interface SiteItem_post extends SiteItemBase {
|
||||||
fetchType: "post";
|
// fetchType: "post";
|
||||||
postParams: Record<string, any>;
|
// postParams: Record<string, any>;
|
||||||
domQuery: DomQuery_parser;
|
// domQuery: DomQuery_parser;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export type SiteItem = SiteItem_get | SiteItem_parser | SiteItem_post;
|
export type SiteItem = SiteItem_get | SiteItem_parser;
|
||||||
|
|
||||||
/** 在线网站列表 */
|
/** 在线网站列表 */
|
||||||
export const siteList: SiteItem[] = [
|
export const siteList: SiteItem[] = [
|
||||||
@@ -66,7 +68,6 @@ export const siteList: SiteItem[] = [
|
|||||||
domQuery: {
|
domQuery: {
|
||||||
subQuery: ".info-header",
|
subQuery: ".info-header",
|
||||||
leakQuery: ".info-header",
|
leakQuery: ".info-header",
|
||||||
videoQuery: ".plyr__controls",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -96,9 +97,13 @@ export const siteList: SiteItem[] = [
|
|||||||
{
|
{
|
||||||
name: "123av",
|
name: "123av",
|
||||||
hostname: "123av.com",
|
hostname: "123av.com",
|
||||||
url: "https://123av.com/zh/v/{{code}}",
|
url: "https://123av.com/zh/search?keyword={{code}}",
|
||||||
fetchType: "get",
|
fetchType: "parser",
|
||||||
domQuery: {},
|
strictParser: true,
|
||||||
|
domQuery: {
|
||||||
|
linkQuery: `.detail>a[href*='v/']`,
|
||||||
|
titleQuery: `.detail>a[href*='v/']`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 有可能搜出仨:leakage subtitle 4k
|
// 有可能搜出仨:leakage subtitle 4k
|
||||||
@@ -117,8 +122,8 @@ export const siteList: SiteItem[] = [
|
|||||||
url: "https://netflav5.com/search?type=title&keyword={{code}}",
|
url: "https://netflav5.com/search?type=title&keyword={{code}}",
|
||||||
fetchType: "parser",
|
fetchType: "parser",
|
||||||
domQuery: {
|
domQuery: {
|
||||||
linkQuery: ".video_grid_container a",
|
linkQuery: ".grid_0_cell>a[href^='/video?']",
|
||||||
titleQuery: ".video_grid_container",
|
titleQuery: ".grid_0_cell>a[href^='/video?'] .grid_0_title",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -233,7 +238,10 @@ export const siteList: SiteItem[] = [
|
|||||||
hostname: "www.av01.tv",
|
hostname: "www.av01.tv",
|
||||||
url: "https://www.av01.tv/search/videos?search_query={{code}}",
|
url: "https://www.av01.tv/search/videos?search_query={{code}}",
|
||||||
fetchType: "parser",
|
fetchType: "parser",
|
||||||
domQuery: { linkQuery: "div[id].well-sm>a", titleQuery: ".video-views>.pull-left" },
|
domQuery: {
|
||||||
|
linkQuery: "div.well>a[href^='/video/']",
|
||||||
|
titleQuery: "div.well>a[href^='/video/']",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "18sex",
|
name: "18sex",
|
||||||
@@ -250,6 +258,7 @@ export const siteList: SiteItem[] = [
|
|||||||
domQuery: { linkQuery: ".well>a[href]", titleQuery: ".well>a[href]>span.video-title" },
|
domQuery: { linkQuery: ".well>a[href]", titleQuery: ".well>a[href]>span.video-title" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// 套了个 cf_clearance 的 cookie,不好搞
|
||||||
name: "evojav",
|
name: "evojav",
|
||||||
hostname: "evojav.pro",
|
hostname: "evojav.pro",
|
||||||
url: "https://evojav.pro/video/{{code}}/",
|
url: "https://evojav.pro/video/{{code}}/",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { gmGet, isCaseInsensitiveEqual, isErrorCode, regEnum, tagsQuery } from "./";
|
import { gmGet, isCaseInsensitiveEqual, isErrorCode, tagsQuery } from "./";
|
||||||
import type { DomQuery_get, DomQuery_parser, SiteItem } from "./siteList";
|
import type { DomQuery_get, SiteItem, SiteItem_parser } from "./siteList";
|
||||||
|
|
||||||
export type FetchResult = {
|
export type FetchResult = {
|
||||||
isSuccess: boolean;
|
isSuccess: boolean;
|
||||||
targetLink?: string;
|
resultLink?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
multipResLink?: string;
|
multipResLink?: string;
|
||||||
multipleRes?: boolean;
|
multipleRes?: boolean;
|
||||||
@@ -16,54 +16,68 @@ function videoPageParser(responseText: string, { subQuery, leakQuery, videoQuery
|
|||||||
const subNode = subQuery ? doc.querySelector<HTMLElement>(subQuery) : "";
|
const subNode = subQuery ? doc.querySelector<HTMLElement>(subQuery) : "";
|
||||||
const subNodeText = subNode ? subNode.innerHTML : "";
|
const subNodeText = subNode ? subNode.innerHTML : "";
|
||||||
const leakNode = leakQuery ? doc.querySelector<HTMLElement>(leakQuery) : null;
|
const leakNode = leakQuery ? doc.querySelector<HTMLElement>(leakQuery) : null;
|
||||||
const linkNodeText = leakNode ? leakNode.innerHTML : "";
|
const leakNodeText = leakNode ? leakNode.innerHTML : "";
|
||||||
|
|
||||||
/** 部分网站收录视频,但是未提供播放资源,所以需要使用 videoQuery 进一步检测是否存在在线播放。
|
/** 部分网站收录视频,但是未提供播放资源,所以需要使用 videoQuery 进一步检测是否存在在线播放。
|
||||||
* videoQuery 为 undefine 时,不需要查找 video
|
* videoQuery 为 undefine 时,不需要查找 video
|
||||||
*/
|
*/
|
||||||
const videoNode = videoQuery ? doc.querySelector<HTMLElement>(videoQuery) : true;
|
const videoNode = videoQuery ? doc.querySelector<HTMLElement>(videoQuery) : true;
|
||||||
return {
|
return {
|
||||||
isSuccess: !!videoNode,
|
isSuccess: !!videoNode,
|
||||||
tag: tagsQuery({ leakageText: linkNodeText, subtitleText: subNodeText }),
|
tag: tagsQuery({ leakageText: leakNodeText, subtitleText: subNodeText }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchPageCodeCheck(
|
||||||
|
titleNodes: NodeListOf<Element> | never[],
|
||||||
|
siteItem: SiteItem_parser,
|
||||||
|
CODE: string,
|
||||||
|
) {
|
||||||
|
if (!titleNodes || titleNodes.length === 0) return { isSuccess: false, titleNodeText: "" };
|
||||||
|
const codeRegex = /[a-zA-Z]{3,5}-\d{3,5}/;
|
||||||
|
if (siteItem.strictParser) {
|
||||||
|
const nodes = Array.from(titleNodes);
|
||||||
|
const passNodes = nodes.filter((node) => {
|
||||||
|
const nodeCode = node.outerHTML.match(codeRegex);
|
||||||
|
return isCaseInsensitiveEqual(nodeCode?.[0], CODE);
|
||||||
|
});
|
||||||
|
const titleNodeText = passNodes.map((node) => node.outerHTML).join(" ");
|
||||||
|
return {
|
||||||
|
titleNodeText,
|
||||||
|
isSuccess: passNodes.length > 0,
|
||||||
|
multipleRes: passNodes.length > 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const titleNode = titleNodes[siteItem.domQuery.listIndex ?? 0];
|
||||||
|
const titleNodeText = titleNode ? titleNode?.outerHTML : "";
|
||||||
|
const matchCode = titleNodeText.match(codeRegex);
|
||||||
|
const isSuccess = isCaseInsensitiveEqual(matchCode?.[0], CODE);
|
||||||
|
return { titleNodeText, isSuccess, multipleRes: titleNodes.length > 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 针对 fetcher==="parser" 时的搜索结果页进行解析,寻找是否存在视频资源。
|
/** 针对 fetcher==="parser" 时的搜索结果页进行解析,寻找是否存在视频资源。
|
||||||
* linkQuery & titleQuery 都是必须,
|
* linkQuery & titleQuery 都是必须,
|
||||||
* linkQuery 有结果且 titleQuery 有结果包含 code,返回 isSuccess。
|
* linkQuery 有结果且 titleQuery 有结果包含 code,返回 isSuccess。
|
||||||
* 再检查下 title 中是否含有字幕信息等
|
* 再检查下 title 中是否含有字幕信息等
|
||||||
*/
|
*/
|
||||||
function serachPageParser(
|
function serachPageParser(responseText: string, siteItem: SiteItem_parser, CODE: string) {
|
||||||
responseText: string,
|
const { linkQuery, titleQuery } = siteItem.domQuery;
|
||||||
{ linkQuery, titleQuery, listIndex = 0 }: DomQuery_parser,
|
|
||||||
siteHostName: string,
|
|
||||||
CODE: string,
|
|
||||||
searchPageLink: string,
|
|
||||||
) {
|
|
||||||
const doc = new DOMParser().parseFromString(responseText, "text/html");
|
const doc = new DOMParser().parseFromString(responseText, "text/html");
|
||||||
|
|
||||||
const titleNodes = titleQuery ? doc.querySelectorAll(titleQuery) : [];
|
const titleNodes = titleQuery ? doc.querySelectorAll(titleQuery) : [];
|
||||||
|
const { isSuccess, titleNodeText, multipleRes } = searchPageCodeCheck(titleNodes, siteItem, CODE);
|
||||||
|
|
||||||
const linkNodes = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery) : [];
|
const linkNodes = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery) : [];
|
||||||
|
const linkNode = linkNodes[siteItem.domQuery.listIndex ?? 0];
|
||||||
const titleNode = titleNodes[listIndex];
|
|
||||||
|
|
||||||
const linkNode = linkNodes[listIndex];
|
|
||||||
const titleNodeText = titleNode ? titleNode?.outerHTML : "";
|
|
||||||
|
|
||||||
const codeRegex = /[a-zA-Z]{3,5}-\d{3,5}/;
|
|
||||||
const matchCode = titleNodeText.match(codeRegex);
|
|
||||||
const isSuccess =
|
|
||||||
linkNode && titleNode && matchCode && isCaseInsensitiveEqual(matchCode[0], CODE);
|
|
||||||
|
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
return { isSuccess: false };
|
return { isSuccess: false };
|
||||||
}
|
}
|
||||||
const targetLinkText = linkNode.href.replace(linkNode.hostname, siteHostName);
|
const resultLinkText = linkNode.href.replace(linkNode.hostname, siteItem.hostname);
|
||||||
return {
|
return {
|
||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
targetLink: targetLinkText,
|
resultLink: resultLinkText,
|
||||||
multipResLink: searchPageLink,
|
multipleRes,
|
||||||
multipleRes: titleNodes.length > 1,
|
|
||||||
tag: tagsQuery({ leakageText: titleNodeText, subtitleText: titleNodeText }),
|
tag: tagsQuery({ leakageText: titleNodeText, subtitleText: titleNodeText }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,24 +98,17 @@ export const baseFetcher = async ({ siteItem, targetLink, CODE }: Args): Promise
|
|||||||
if (siteItem.fetchType === "get") {
|
if (siteItem.fetchType === "get") {
|
||||||
// 直接 get 网页,成功,需要进一步解析 videoPage,获取字幕等信息
|
// 直接 get 网页,成功,需要进一步解析 videoPage,获取字幕等信息
|
||||||
return {
|
return {
|
||||||
|
resultLink: targetLink,
|
||||||
...videoPageParser(response.responseText, siteItem.domQuery),
|
...videoPageParser(response.responseText, siteItem.domQuery),
|
||||||
targetLink,
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...serachPageParser(
|
...serachPageParser(response.responseText, siteItem, CODE),
|
||||||
response.responseText,
|
|
||||||
siteItem.domQuery,
|
|
||||||
siteItem.hostname,
|
|
||||||
CODE,
|
|
||||||
targetLink,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
isSuccess: false,
|
isSuccess: false,
|
||||||
targetLink: targetLink,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user