103 Commits
newui ... main

Author SHA1 Message Date
mrbunker
a9ab81a6e4 release: 1.2.11 2025-05-05 18:19:28 +08:00
mrbunker
a33b92df89 feat: update sitelist 2025-05-05 18:17:09 +08:00
mrbunker
ed1e9f1cf7 release: 1.2.10 2025-04-20 14:12:58 +08:00
mrbunker
a0704add61 feat: fetchtype 'false' 2025-04-20 14:10:08 +08:00
mrbunker
eef55334ab chore: update package 2025-04-20 13:54:09 +08:00
mrbunker
80ac0a2778 release: 1.2.9 2025-01-17 20:49:49 +08:00
mrbunker
1559d3317a feat: update sitelist 2025-01-17 20:49:02 +08:00
mrbunker
4481afcf3b release: 1.2.8 2025-01-05 22:41:39 +08:00
mrbunker
2ab10ea667 feat: improve handling of search results 2025-01-05 22:41:36 +08:00
mrbunker
2ff919c8ca release: 1.2.7 2025-01-05 13:52:58 +08:00
mrbunker
5494ad61b0 fix: improve error handling 2025-01-05 13:41:09 +08:00
mrbunker
43b56b119d fix: @require is restricted 2024-12-29 01:02:07 +08:00
mrbunker
4eeb272e13 release: 1.2.6 2024-12-29 00:39:45 +08:00
mrbunker
ffdc72f6bc feat: update sitelist 2024-12-29 00:39:04 +08:00
mrbunker
82c78a0ccf feat: match more lib domains 2024-12-29 00:16:22 +08:00
mrbunker
7371dd6a32 fix: requests are serialized 2024-12-28 22:08:01 +08:00
mrbunker
e9ca6f8563 chore: upgrade ependencies 2024-12-28 20:56:32 +08:00
mrbunker
a3d1310fef release: 1.2.5 2024-10-06 18:26:48 +08:00
mrbunker
d34f805c43 fix: button not opening default link 2024-10-06 18:26:11 +08:00
mrbunker
e1612aa1d7 release: 1.2.4 2024-09-22 11:54:31 +08:00
mrbunker
7bf4ee401b fix: settings panel closed by default 2024-09-22 11:53:51 +08:00
mrbunker
cfd86d563b release: 1.2.3 2024-09-21 18:05:58 +08:00
mrbunker
1e444db50f fix: multipleFlag didn't work 2024-09-21 18:03:28 +08:00
mrbunker
e6a6201429 release: 1.2.2 2024-09-21 18:02:37 +08:00
mrbunker
7e3044a0cf feat: hidden error result 2024-09-21 17:56:26 +08:00
mrbunker
93a51282b6 release: 1.2.0 2024-09-21 16:46:23 +08:00
mrbunker
ea9e20931a fix: tag not displaying 2024-09-21 16:38:32 +08:00
mrbunker
7c0ea49fb1 feat: show search page for multiple search results 2024-09-21 16:28:55 +08:00
mrbunker
476dd008cd feat: checkbox component 2024-09-21 15:11:19 +08:00
mrbunker
f37890e59e release: 1.1.17 2024-06-23 00:02:01 +08:00
mrbunker
de2ab8f506 feat: upadte sitelist 2024-06-23 00:01:19 +08:00
mrbunker
274245f8ff release: 1.1.16 2024-01-06 03:12:35 +08:00
mrbunker
7dd3afddff feat: post method 2024-01-06 03:10:50 +08:00
mrbunker
15138866c8 feat: custom fetch method for jable 2024-01-06 02:32:57 +08:00
mrbunker
828803e618 fix: update sitelist 2024-01-06 01:13:34 +08:00
mrbunker
e0c6c0e02a feat: recognize code start with 300 2024-01-06 00:25:10 +08:00
mrbunker
b40ac410a2 chore: update npm package 2024-01-05 23:56:19 +08:00
mrbunker
0fcaaafa81 release: 1.1.15 2023-09-10 23:27:42 +08:00
mrbunker
98fabed547 fix: Improved domain regex 2023-09-10 23:23:02 +08:00
mrbunker
e738d6a90a release: 1.1.14 2023-07-29 16:48:22 +08:00
mrbunker
b55e67e8f2 fix: JAVMENU 2023-07-29 16:45:06 +08:00
mrbunker
cba1689206 fix: remove leakquery of MISSAV 2023-07-29 16:24:35 +08:00
mrbunker
59a83bdb12 feat: new site 18av 2023-07-29 16:23:09 +08:00
mrbunker
35e0646f7f fix: avjoy, incorrect match 2023-07-29 15:58:48 +08:00
mrbunker
f1fc11c81d refactor: xrh 2023-07-29 15:14:37 +08:00
mrbunker
001b22f9e6 update npm package 2023-06-14 22:01:40 +08:00
mrbunker
daf3a10ff6 release: 1.1.13 2023-05-20 15:22:52 +08:00
mrbunker
de3e1fe8b0 feat: custom formatting of CODE 2023-05-20 15:17:07 +08:00
mrbunker
3e4ea02b87 fix: resolve CODE sensitivity 2023-05-20 15:02:36 +08:00
mrbunker
fe5e1b0bcd feat: add new site missav_(noproxy) 2023-05-20 15:01:17 +08:00
mrbunker
45390b82e5 release: 1.1.12 2023-04-14 22:53:01 +08:00
mrbunker
0378be64ba release: 1.1.12 2023-04-14 22:51:58 +08:00
mrbunker
f252c91c69 fix: use regex to fix incorrect CODE matching 2023-04-14 22:50:59 +08:00
mrbunker
110da4c67f release: 1.1.11 2023-04-09 18:33:29 +08:00
mrbunker
fb9f9ffa4c feat: add new sites 2023-04-09 18:32:54 +08:00
mrbunker
d833c6ada7 release: 1.1.10 2023-04-07 00:08:51 +08:00
mrbunker
cfa98eee91 fix: njav error return (always success) 2023-04-07 00:04:06 +08:00
mrbunker
c105e2e7e3 feat: support 'bid' domain of jbus 2023-04-06 23:51:28 +08:00
mrbunker
9aad0bca35 release: 1.1.9 2023-04-06 23:03:03 +08:00
mrbunker
8ea071edd5 chore: update search for leak 2023-04-06 23:02:45 +08:00
mrbunker
5f2005420e feat: add new site(njav) 2023-04-06 22:00:55 +08:00
mrbunker
9732e682c0 release: 1.1.8 2023-04-05 20:34:47 +08:00
mrbunker
0eeb0e2fe0 feat: add search result parser for javlib 2023-04-05 20:31:17 +08:00
mrbunker
33db14c17c fix: javlib not skipping oneself 2023-04-05 19:57:46 +08:00
mrbunker
c388582d72 fix: GM_getValue not working # json 不支持 Set 类型 2023-04-05 19:54:55 +08:00
mrbunker
278c768d24 chore: update sites of @connect 2023-04-05 19:49:46 +08:00
mrbunker
dd4892cbe6 fix: disable the treatment for spaceCode 2023-04-05 18:21:40 +08:00
mrbunker
9a728ab537 defDis is now of Set type instead of array type 2023-04-05 18:15:40 +08:00
mrbunker
1ec433e1b1 release: 1.1.7 2023-03-11 18:27:40 +08:00
mrbunker
794376a653 format the dist file 2023-03-11 14:41:11 +08:00
mrbunker
30e958764d fix incorrect cache after hidden a site 2023-03-11 14:40:15 +08:00
mrbunker
cde6f34286 release: 1.1.6 2023-03-11 13:45:54 +08:00
mrbunker
6d87fbc0a4 add key to skip the unnecessary render of SiteBtn 2023-03-11 13:44:51 +08:00
mrbunker
db1f5d22b8 optimize the props of disables 2023-03-11 13:41:47 +08:00
mrbunker
486d5244d8 release: 1.1.5 2023-03-11 12:33:56 +08:00
mrbunker
2818fae6af fix the error redirect of jable 2023-03-11 12:32:23 +08:00
mrbunker
cb3a6ceb2c add javlib into siteList 2023-03-11 11:36:00 +08:00
mrbunker
96c6948c6a test 2023-01-20 22:19:44 +08:00
mrbunker
b1a5f7773b test action 2023-01-20 22:16:16 +08:00
mrbunker
5c7838b4ee Create main.yml 2023-01-20 22:07:42 +08:00
mrbunker
747ed2dbb7 add [javdb] into sitelist 2023-01-20 21:59:55 +08:00
mrbunker
a9dc863e97 build 2023-01-01 20:10:01 +08:00
mrbunker
7028723eb1 chore 2023-01-01 19:45:30 +08:00
mrbunker
d8f0257d6b support fc2 2023-01-01 19:45:21 +08:00
mrbunker
205d9e8983 Merge branch 'main' of https://github.com/Mrbunker/userjs-jop 2023-01-01 19:22:27 +08:00
mrbunker
eb521a9274 update des 2023-01-01 19:19:11 +08:00
mrbunker
790d227d67 update viteConfig: charset utf8 2022-12-30 09:50:16 +08:00
mrbunker
e616321382 Update README.md 2022-11-30 17:25:30 +08:00
mrbunker
14132c260a 修复自定义设置无效 2022-10-29 23:16:13 +08:00
mrbunker
3a6da84e88 - 新网站 -feat:自定义需要默认显示的网站 2022-10-23 22:56:48 +08:00
mrbunker
17febed302 Merge branch 'lite' 2022-10-23 18:13:57 +08:00
mrbunker
018b8a273f 精简掉面板,回到归只有按钮 2022-10-23 18:13:09 +08:00
mrbunker
3c27f5f7c8 branch lite inite 2022-10-21 22:54:27 +08:00
mrbunker
14109a966c update todolist 2022-10-21 22:54:05 +08:00
mrbunker
f575e0f104 todo:siteList 自定义 2022-10-17 00:37:01 +08:00
mrbunker
6773171b9e score 2022-10-16 23:12:02 +08:00
mrbunker
31acbf5e25 chore:别名路径等 2022-10-12 23:49:03 +08:00
mrbunker
77377578ad Merge branch 'main' of https://github.com/Mrbunker/userjs-jop 2022-10-12 22:06:45 +08:00
mrbunker
1af5aca88c 适配 jav 老司机 2022-10-12 22:06:40 +08:00
mrbunker
83b76175b4 添加 Info 非空判断 2022-10-12 22:06:12 +08:00
mrbunker
509bf41ccd Update vite.config.ts 2022-10-10 08:48:50 +08:00
mrbunker
4efd43b329 test 2022-10-10 00:59:02 +08:00
mrbunker
1ac71be2ce Create README.md 2022-10-03 21:15:55 +08:00
31 changed files with 3383 additions and 2779 deletions

4
.gitignore vendored
View File

@@ -13,7 +13,7 @@ dist-ssr
*.local
# Editor directories and files
.vscode/*
# .vscode/*
!.vscode/extensions.json
.idea
.DS_Store
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# **/buckup

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"printWidth": 100,
"trailingComma": "all",
"quoteProps": "preserve",
"jsxSingleQuote": false,
"singleQuote": false,
"singleAttributePerLine": false
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"editor.formatOnSaveMode": "modificationsIfAvailable",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "./node_modules/typescript/lib"
}

View File

@@ -1,28 +1,12 @@
**userscript**
vite-plugin-monkey + preact
```sh
pnpm i
pnpm dev
pnpm build
pnpm preview
```
### todo
- 删减列表
- 演员头像,偷 bus
```html
<img src="/pics/actress/sl1_a.jpg" title="河北彩花">
```
- 移动端 样式改动panel 的阴影、圆角等)
- 更多域名
- lib 弹窗 zindex
- hook buttons
```js
const rbu = document.querySelector(`a[href="#magnet-links"]`) as HTMLElement;
console.log(rbu);
const rbuRef = useRef<HTMLElement>(rbu);
rbuRef.current.click();
```

1681
dist/jop.user.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>jop</title>
<!-- /index.html 必须存在, 否则 vite 无法注入 hmr 代码, plugin-monkey 以及其他插件 也无法注入相关辅助代码 -->
<!-- /index.html must exist, if not, vite will not inject hmr code, plugin-monkey and others will not inject theirs related auxiliary code -->
</head>
</html>

View File

@@ -5,15 +5,17 @@
"type": "module",
"scripts": {
"dev": "vite",
"preview": "vite preview",
"build": "tsc && vite build"
},
"dependencies": {
"preact": "10.11.0"
"@types/node": "^22.14.1",
"preact": "10.25.4"
},
"devDependencies": {
"@preact/preset-vite": "^2.4.0",
"typescript": "^4.8.4",
"vite": "^3.1.4",
"vite-plugin-monkey": "^2.5.1"
"@preact/preset-vite": "^2.10.1",
"typescript": "^5.8.3",
"vite": "^6.3.2",
"vite-plugin-monkey": "^5.0.8"
}
}

2091
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

63
script/urlConfig.ts Normal file
View File

@@ -0,0 +1,63 @@
import { siteList } from "../src/utils/siteList";
const cleanUrl = (url: string): string => {
const domainMatch = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/i);
if (!domainMatch) return "";
const domain = domainMatch[1];
return `*://*.${domain}/*`;
};
const getLibMirror = async () => {
if (process.env.NODE_ENV !== "production") {
return "";
}
try {
const user = "javlibcom";
const res = await fetch(`https://api.github.com/users/${user}`);
const data = await res.json();
return cleanUrl(data.blog ?? "");
} catch (error) {
console.warn("Error fetching lib mirror:", error);
return "";
}
};
const getDbMirrors = async () => {
if (process.env.NODE_ENV !== "production") {
return "";
}
try {
const res = await fetch(`https://javdb.com/`);
const htmlText = await res.text();
const navMatch = htmlText.match(
/<nav[^>]*class="[^"]*sub-header[^"]*"[^>]*>([\s\S]*?)<\/nav>/i,
);
if (!navMatch) return [];
const hrefRegex = /href="([^"]+)"/g;
return [...navMatch[1].matchAll(hrefRegex)].map((match) => cleanUrl(match[1])).filter(Boolean);
} catch (error) {
console.warn("Error fetching db mirror:", error);
return [];
}
};
const libMirrorUrl = await getLibMirror();
const dbMirrorUrls = await getDbMirrors();
const connectList = siteList.map((site) => site.hostname);
const includeList = [
/^https?:\/\/(\w*\.)?javdb(\d)*\.com\/v.*$/,
/^https?:\/\/(\w*\.)?(javbus|seejav|javsee)*\.(com|cc|me|life|bid).*$/,
/^https?:\/\/(\w*\.)?javlibrary\.com.*$/,
/^http.*\/cn\/\?v=jav.*$/,
];
const mirrorList = [...dbMirrorUrls, libMirrorUrl].filter(Boolean);
export default {
match: [...mirrorList],
connect: connectList,
include: includeList,
};

View File

@@ -1,31 +0,0 @@
import { createButtonNode, createPanel } from "./utils/createNode";
import { matchList } from "./utils/matchList";
import { siteList } from "./utils/siteList";
import { xhr } from "./utils/xhr";
import type { Cms } from "./utils/matchList";
import "./style.css";
export async function main() {
/** 当前 macth 站点对象 */
const cms = matchList.find((item) => item.hostname === window.location.hostname) as Cms;
const CODE = getCode(cms.name);
if (CODE === undefined) return;
const panel = createPanel(cms);
/** 禁用部分 */
const envSiteList = siteList.filter((item) => item.disable !== cms.name);
envSiteList.forEach(async (siteItem) => {
const siteUrl = siteItem.url.replace("{{code}}", CODE);
const { setButtonStatus } = createButtonNode(panel, siteItem.name, siteUrl);
const { isSuccess, hasLeakage, hasSubtitle, targetLink } = await xhr(siteItem, siteUrl, CODE);
setButtonStatus(targetLink, isSuccess ? "green" : "red", hasLeakage, hasSubtitle);
});
}
main();

View File

@@ -1,81 +1,58 @@
import { memo } from "preact/compat";
import { useEffect, useState } from "preact/hooks";
import xhr from "../utils/xhr";
import { Cms } from "../utils/matchList";
import { siteList } from "../utils/siteList";
import Info, { Infos } from "./Info";
import SiteButton from "./SiteButton";
import { GM_getValue } from "vite-plugin-monkey/dist/client";
import Top from "./Top";
export type RenderSiteItem = {
name: string;
targetLink: string;
status: {
isSuccess: "pedding" | "rejected" | "fulfilled";
hasSubtitle: boolean;
hasLeakage: boolean;
};
};
const App = memo(function ({ cms, infos, CODE }: { cms: Cms; infos: Infos; CODE: string }) {
// !todo hook buttons
// const rbu = document.querySelector(`a[href="#magnet-links"]`) as HTMLElement;
// console.log(rbu);
// const rbuRef = useRef<HTMLElement>(rbu);
// rbuRef.current.click();
const gmShowPanel = GM_getValue("setting", { gmShowPanel: true }).gmShowPanel;
const [showPanel, setShowPanel] = useState(gmShowPanel);
/** 禁用 disable */
const initSiteList = siteList.filter((item) => item.disable !== cms.name);
const [renderSiteList, setRenderSiteList] = useState<RenderSiteItem[]>(
initSiteList.map((item) => ({
name: item.name,
targetLink: item.url.replace("{{code}}", CODE),
status: { isSuccess: "pedding", hasSubtitle: false, hasLeakage: false },
})),
);
useEffect(() => {
initSiteList.forEach(async (siteItem, index) => {
const targetLink = siteItem.url.replace("{{code}}", CODE);
const result = await xhr(siteItem, targetLink, CODE);
renderSiteList[index] = {
name: siteItem.name,
targetLink,
status: {
isSuccess: result.isSuccess ? "fulfilled" : "rejected",
hasLeakage: result.hasLeakage,
hasSubtitle: result.hasSubtitle,
},
};
setRenderSiteList([...renderSiteList]);
});
}, [xhr, setRenderSiteList]);
// }, []);
return (
<>
{showPanel && (
<div className="jop-panel">
<Info infos={infos} />
<div className="jop-list">
{renderSiteList.map((item) => {
return <SiteButton itemData={item} />;
})}
</div>
</div>
)}
<Top
showPanel={showPanel}
setShowPanel={setShowPanel}
/>
</>
);
});
export default App;
import { memo, useState } from "preact/compat";
import Setting from "./Setting";
import SiteBtn from "./SiteBtn";
import { GM_getValue, GM_setValue } from "$";
import { SiteItem, siteList } from "@/utils/siteList";
import type { LibItem } from "@/utils/libSites";
const App = memo(function ({ libItem, CODE }: { libItem: LibItem; CODE: string }) {
// 默认不显示
const DEF_DIS = [
...["AvJoy", "baihuse", "GGJAV", "AV01", "18sex", "highporn", "evojav", "HAYAV"],
...["JavBus", "JavDB", "JAVLib", "MISSAV_", "123av", "javhub", "javgo", "JAVMENU"],
];
const [disables, setDisables] = useState(GM_getValue<SiteItem["name"][]>("disable", DEF_DIS));
const [multipleNavi, setMultipleNavi] = useState(GM_getValue<boolean>("multipleNavi", true));
const [hiddenError, setHiddenError] = useState(GM_getValue<boolean>("hiddenError", false));
const list = siteList.filter(
(siteItem) => !disables.includes(siteItem.name) && !siteItem.hostname.includes(libItem.name),
);
return (
<>
<div class="jop-list">
{list.map((siteItem) => (
<SiteBtn
siteItem={siteItem}
CODE={CODE}
key={siteItem.name}
multipleNavi={multipleNavi}
hiddenError={hiddenError}
/>
))}
</div>
<Setting
siteList={siteList}
setDisables={(disable) => {
setDisables(disable);
GM_setValue("disable", disable);
}}
multipleNavi={multipleNavi}
setMultipleNavi={(multipleNavi) => {
setMultipleNavi(multipleNavi);
GM_setValue("multipleNavi", multipleNavi);
}}
disables={disables}
hiddenError={hiddenError}
setHiddenError={(v) => {
setHiddenError(v);
GM_setValue("hiddenError", v);
}}
/>
</>
);
});
export default App;

View File

@@ -0,0 +1,31 @@
import Tooltip from "./Tooltip";
interface CheckboxProps {
label: string;
value: boolean;
tip?: string;
onChange: (checked: boolean) => void;
}
const Checkbox = ({ label, value, tip, onChange }: CheckboxProps): JSX.Element => {
const handleChange = (event: JSX.TargetedEvent<HTMLInputElement>) => {
onChange(event.currentTarget.checked);
};
return (
<label className="jop-checkbox">
<input
type="checkbox"
className="jop-checkbox-input"
checked={value}
onChange={handleChange}
/>
<span className="jop-checkbox-custom"></span>
<Tooltip content={tip || ""}>
<span className="jop-checkbox-label">{label}</span>
</Tooltip>
</label>
);
};
export default Checkbox;

View File

@@ -1,2 +0,0 @@
// !todo
export {};

View File

@@ -1,40 +0,0 @@
import { memo } from "preact/compat";
/** 从原 info panel 抄一点精简的信息 */
export type Infos = {
codeText: string;
actorList: {
text: string;
link: string;
// avatart:string
}[];
};
const Info = memo(({ infos }: { infos: Infos }) => {
return (
<div className="jop-info">
<span
className="jop-info-code"
title="点击复制"
onClick={() => navigator.clipboard.writeText(infos.codeText)}
>
{infos.codeText}
</span>
<div class="jop-info-actor">
<a
class="jop-info-actor-item"
target="_blank"
href={infos.actorList[0].link}
>
{infos.actorList[0].text}
</a>
<span> </span>
</div>
{/* {info.actorList.map((item, index) => { const length = info.actorList.length; return ( <a href={item.link} style={{ paddingRight: length !== 1 && index !== length - 1 ? 16 : 0 }} > {item.text} </a> ); })} */}
</div>
);
});
export default Info;

106
src/components/Setting.tsx Normal file
View File

@@ -0,0 +1,106 @@
import { Dispatch, StateUpdater, useState } from "preact/hooks";
import { GM_setValue } from "$";
import { SiteItem } from "@/utils/siteList";
import Checkbox from "./Checkbox";
type Props = {
siteList: SiteItem[];
setDisables: Dispatch<StateUpdater<string[]>>;
disables: SiteItem["name"][];
multipleNavi: boolean;
setMultipleNavi: Dispatch<StateUpdater<boolean>>;
hiddenError: boolean;
setHiddenError: Dispatch<StateUpdater<boolean>>;
};
const Setting = ({
siteList,
setDisables,
disables,
multipleNavi,
setMultipleNavi,
hiddenError,
setHiddenError,
}: Props) => {
const [showSetting, setShowSetting] = useState(false);
const hanleListChange = (item: SiteItem, isHidden: boolean) => {
if (isHidden) {
setDisables(disables.filter((disItem) => disItem !== item.name));
} else {
setDisables([...disables, item.name]);
}
};
const handleNaviChange = (checked: boolean) => {
setMultipleNavi(checked);
GM_setValue("multipleNavi", checked);
};
const handlehiddenErrorChange = (checked: boolean) => {
setHiddenError(checked);
GM_setValue("hiddenError", checked);
};
return (
<>
{!showSetting && (
<div className="jop-button_def" onClick={() => setShowSetting(!showSetting)}>
</div>
)}
{showSetting && (
<>
<div className="jop-setting">
<Group title="勾选默认展示">
{siteList.map((item) => {
const isHidden = disables.includes(item.name);
return (
<Checkbox
label={item.name}
value={!isHidden}
onChange={(checked) => hanleListChange(item, checked)}
/>
);
})}
</Group>
<Group title="其他设置">
<Checkbox
label="展示多个搜索结果"
value={multipleNavi}
tip="一个站点内出现多条匹配结果时,打开后跳转搜索结果页"
onChange={handleNaviChange}
/>
<Checkbox
label="隐藏失败结果"
value={hiddenError}
onChange={handlehiddenErrorChange}
/>
</Group>
</div>
<div
className="jop-button_def"
onClick={() => {
setShowSetting(!showSetting);
}}
>
</div>
</>
)}
</>
);
};
const Group = ({ title, children }: { title: string; children: React.ReactNode }) => {
return (
<>
<h4 className="jop-setting-title">{title}</h4>
<div className="jop-setting-list">{children}</div>
</>
);
};
export default Setting;

View File

@@ -0,0 +1,52 @@
import { fetcher, FetchResult } from "@/utils/xhr";
import { SiteItem } from "@/utils/siteList";
import { useEffect, useState } from "preact/compat";
type Props = {
siteItem: SiteItem;
CODE: string;
multipleNavi?: boolean;
hiddenError?: boolean;
};
const SiteBtn = ({ siteItem, CODE, multipleNavi, hiddenError }: Props) => {
const { name, codeFormater } = siteItem;
/** 格式化 CODE */
const formatCode = codeFormater ? codeFormater(CODE) : CODE;
const originLink = siteItem.url.replace("{{code}}", formatCode);
const [loading, setLoading] = useState(false);
const [fetchRes, setFetchRes] = useState<FetchResult>();
useEffect(() => {
setLoading(true);
fetcher({ siteItem, targetLink: originLink, CODE: formatCode }).then((res) => {
setFetchRes(res);
setLoading(false);
});
}, [fetcher, siteItem, CODE, originLink]);
const multipleFlag = multipleNavi && fetchRes?.multipleRes;
const tag = multipleFlag ? "多结果" : fetchRes?.tag;
const resultLink = multipleFlag ? originLink : fetchRes?.resultLink;
const colorClass = fetchRes?.isSuccess ? "jop-button_green " : "jop-button_red ";
if (hiddenError && !fetchRes?.isSuccess) {
return <></>;
}
return (
<a
className={"jop-button " + (loading ? " " : colorClass)}
target="_blank"
href={!resultLink ? originLink : resultLink}
>
{tag && <div className="jop-button_label">{tag}</div>}
{/* 加载动画 */}
{/* {isSuccess === "pedding" && <span className="jop-loading"> </span>} */}
<span>{name}</span>
</a>
);
};
export default SiteBtn;

View File

@@ -1,35 +0,0 @@
import { memo } from "preact/compat";
import { RenderSiteItem } from "./App";
const SiteButton = memo(({ itemData }: { itemData: RenderSiteItem }) => {
const { name, targetLink, status } = itemData;
const { isSuccess, hasSubtitle, hasLeakage } = status;
// console.log("sitebutton render");
const colorClass =
isSuccess === "pedding"
? " "
: isSuccess === "fulfilled"
? "jop-button_green "
: "jop-button_red ";
return (
<a
className={"jop-button " + colorClass}
target="_blank"
href={targetLink}
>
{(hasSubtitle || hasLeakage) && (
<div className="jop-button_label">
{hasSubtitle && <span> </span>}
{hasLeakage && <span> </span>}
</div>
)}
{/* 加载动画 */}
{/* {isSuccess === "pedding" && <span className="jop-loading"> </span>} */}
<span>{name}</span>
</a>
);
});
export default SiteButton;

View File

@@ -0,0 +1,23 @@
import { useState } from "preact/hooks";
interface TooltipProps {
content: string;
children: preact.ComponentChildren;
}
const Tooltip = ({ content, children }: TooltipProps) => {
const [isVisible, setIsVisible] = useState(false);
return (
<div
className="jop-tooltip-container"
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
>
{children}
{isVisible && content && <div className="jop-tooltip">{content}</div>}
</div>
);
};
export default Tooltip;

View File

@@ -1,197 +0,0 @@
import { GM_getValue, GM_setValue } from "$";
import { useState } from "preact/hooks";
// const Setting = () => {
// };
// const CloseBtn = ({
// showPanel,
// setShowPanel,
// }: {
// showPanel: boolean;
// setShowPanel: (showPanel: boolean) => void;
// }) => {
// return
// };
const Top = ({
showPanel,
setShowPanel,
}: {
showPanel: boolean;
setShowPanel: (showPanel: boolean) => void;
}) => {
const [showSettingPanel, setShowSettingPanel] = useState(false);
const gmShowPanel = GM_getValue("setting", { gmShowPanel: true }).gmShowPanel;
return (
<div className="jop-top">
{showPanel && (
<div
className="jop-top-setting jop-top-item"
onClick={(e) => {
e.stopPropagation();
setShowSettingPanel(!showSettingPanel);
}}
>
<div className="jop-top-setting-svg jop-top-svgicon">
<svg
width="25"
height="25"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M41.5 10H35.5"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M27.5 6V14"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M27.5 10L5.5 10"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13.5 24H5.5"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M21.5 20V28"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M43.5 24H21.5"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M41.5 38H35.5"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M27.5 34V42"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M27.5 38H5.5"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</div>
)}
{showSettingPanel && showPanel && (
<div className="jop-top-settingPanel">
<h4 className="jop-top-setting-title"></h4>
<div className="jop-top-settingPanel-item">
<input
type="checkbox"
className="jop-top-checkbox"
checked={gmShowPanel}
onChange={(e: any) => {
const checked: boolean = e.target.checked;
GM_setValue("setting", { gmShowPanel: checked });
setShowPanel(checked);
}}
/>
</div>
</div>
)}
<div
className="jop-top-close jop-top-item jop-top-svgicon"
onClick={(e) => {
e.stopPropagation();
setShowPanel(!showPanel);
}}
>
{showPanel ? (
<svg
width="25"
height="25"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.85786 18C6.23858 21 4 24 4 24C4 24 12.9543 36 24 36C25.3699 36 26.7076 35.8154 28 35.4921M20.0318 12.5C21.3144 12.1816 22.6414 12 24 12C35.0457 12 44 24 44 24C44 24 41.7614 27 38.1421 30"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M20.3142 20.6211C19.4981 21.5109 19 22.6972 19 23.9998C19 26.7612 21.2386 28.9998 24 28.9998C25.3627 28.9998 26.5981 28.4546 27.5 27.5705"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M42 42L6 6"
stroke="#333"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
) : (
<svg
width="25"
height="25"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M24 36C35.0457 36 44 24 44 24C44 24 35.0457 12 24 12C12.9543 12 4 24 4 24C4 24 12.9543 36 24 36Z"
fill="none"
stroke="#333"
stroke-width="3"
stroke-linejoin="round"
/>
<path
d="M24 29C26.7614 29 29 26.7614 29 24C29 21.2386 26.7614 19 24 19C21.2386 19 19 21.2386 19 24C19 26.7614 21.2386 29 24 29Z"
fill="none"
stroke="#333"
stroke-width="3"
stroke-linejoin="round"
/>
</svg>
)}
</div>
</div>
);
};
export default Top;

View File

@@ -1,33 +1,39 @@
import React from "preact/compat";
import { render } from "preact";
import { Cms, matchList } from "./utils/matchList";
import { getInfos } from "./utils/getInfos";
import "./style.css";
import App from "./components/App";
// !debugger 图片关
// document.querySelectorAll("img").forEach((item) => (item.style.display = "none"));
function main() {
/** 当前 macth 站点对象 */
const cms = matchList.find((item) => item.href.test(window.location.href)) as Cms;
const infos = getInfos(cms);
const CODE = infos.codeText;
if (CODE === undefined) return;
cms.method();
const panelParent = document.querySelector(cms.panelParentQueryStr) as Element;
panelParent?.classList.add("jop-panelParent");
render(
<App
cms={cms}
CODE={CODE}
infos={infos}
/>,
panelParent,
);
}
main();
import { render } from "preact";
import { libSites } from "@/utils/libSites";
import { getCode } from "@/utils";
import "@/style.css";
import App from "./components/App";
if (!import.meta.env.PROD) {
// document.querySelectorAll("img").forEach((item) => (item.style.visibility = "hidden"));
}
function main() {
/** 当前匹配的图书馆站点对象 */
const libItem = libSites.find((item) => document.querySelector(item.identifier));
if (!libItem) {
console.error("||jop 匹配站点失败");
return;
}
const CODE = getCode(libItem);
// 执行对于当前图书馆站的特殊适配,如单独的样式改动
libItem.method();
const panel = document.querySelector<HTMLElement>(libItem.querys.panelQueryStr);
if (!panel) {
console.error("||jop 插入界面失败");
return;
}
const app = document.createElement("div");
app.classList.add("jop-app");
// app.classList.add(a.toString());
panel.append(app);
render(<App libItem={libItem} CODE={CODE} />, app);
console.log("||脚本挂载成功", CODE);
}
main();

View File

@@ -1,180 +1,226 @@
.jop-panelParent {
position: relative;
}
.jop-panel {
box-sizing: border-box;
position: absolute;
top: 0;
right: -3px;
width: 100%;
height: 100%;
z-index: 1;
padding: 17.5px;
/* border-radius: 15px 0 0 15px; */
background-color: white;
/* box-shadow: rgb(0 0 0 / 26%) -3px 0px 8px; */
transition: right 200ms ease-in-out;
}
/* */
.jop-top {
position: absolute;
width: 100%;
z-index: 1;
}
.jop-top-item {
position: absolute;
top: 20px;
width: 40px;
height: 40px;
cursor: pointer;
}
.jop-top-close {
right: 10px;
}
.jop-top-setting {
position: absolute;
right: 50px;
}
.jop-top-settingPanel {
position: absolute;
top: 60px;
right: 20px;
width: 230px;
height: 150px;
padding: 20px;
border-radius: 4px;
box-shadow: rgb(0 0 0 / 26%) 0px -3px 8px;
background: white;
}
.jop-top-settingPanel-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.jop-top-setting-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
}
.jop-top-checkbox {
margin-left: 20px;
width: 14px;
height: 14px;
}
/* */
.jop-info {
color: black;
/* text-align: right; */
height: 30px;
}
.jop-info-code {
margin-bottom: 20px;
color: #3272dc;
font-size: 30px;
cursor: pointer;
font-family: system-ui, -apple-system;
font-weight: bolder;
font-style: italic;
}
.jop-info-actor {
margin-top: 6px;
}
.jop-info-actor-item {
padding: 3px 6px;
color: #409eff;
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 4px;
}
/* */
.jop-list {
position: absolute;
top: 115px;
width: 80%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 12px;
}
.jop-button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
/* min-width: 50px; */
padding: 3px 10px;
border-radius: 4px;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-weight: 500;
font-size: 14px;
border: 1px solid #dcdfe6;
color: #606266;
}
.jop-button:visited {
color: #606266;
}
.jop-button:hover {
text-decoration: none;
color: #409eff;
border: 1px solid #c6e2ff;
background-color: #ecf5ff;
}
.jop-button_label {
position: absolute;
font-size: 10px;
padding: 4px;
border-radius: 4px;
top: -13px;
right: -10px;
line-height: 0.75;
color: #67c23a;
border: 1px solid #e1f3d8;
background: white;
}
.jop-button_green {
color: white !important;
background-color: #67c23a;
/* border: 1px solid #b3e19d; */
}
.jop-button_green:hover {
color: white !important;
background-color: #95d475;
}
.jop-button_red {
color: white !important;
background-color: #f56c6c;
/* border: 1px solid #fab6b6; */
}
.jop-button_red:hover {
color: white !important;
background-color: #f89898;
}
.jop-loading {
display: inline-block;
width: 14px;
height: 14px;
margin-right: 10px;
border: 2px dashed #dcdfe6;
border-top-color: transparent;
border-radius: 100%;
animation: btnLoading infinite 1s linear;
}
@keyframes btnLoading {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
/* */
.jop-list {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 10px;
width: 100%;
height: 100%;
z-index: 1;
transition: right 200ms ease-in-out;
color: black;
}
/* */
.jop-button,
.jop-button_def {
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 3px 10px;
border-radius: 4px;
font-weight: 500;
font-size: 14px;
border: 1px solid #dcdfe6;
color: #606266;
cursor: pointer;
}
.jop-button_def {
margin: 10px 0;
width: 100px;
}
.jop-button:visited {
color: #606266;
}
.jop-button:hover {
text-decoration: none;
color: #409eff;
border: 1px solid #c6e2ff;
background-color: #ecf5ff;
}
.jop-button_label {
position: absolute;
font-size: 10px;
padding: 4px;
border-radius: 4px;
top: -13px;
right: -10px;
line-height: 0.75;
color: #67c23a;
border: 1px solid #e1f3d8;
background: white;
}
.jop-button_green {
color: white !important;
background-color: #67c23a;
/* border: 1px solid #b3e19d; */
}
.jop-button_green:hover {
color: white !important;
background-color: #95d475;
}
.jop-button_red {
color: white !important;
background-color: #f56c6c;
/* border: 1px solid #fab6b6; */
}
.jop-button_red:hover {
color: white !important;
background-color: #f89898;
}
.jop-loading {
display: inline-block;
width: 14px;
height: 14px;
margin-right: 10px;
border: 2px dashed #dcdfe6;
border-top-color: transparent;
border-radius: 100%;
animation: btnLoading infinite 1s linear;
}
@keyframes btnLoading {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
/* */
.jop-tag {
padding: 3px 6px;
color: #409eff !important;
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 4px;
}
/* */
.jop-setting{
margin-top:20px;
}
.jop-setting-list {
display: flex;
flex-wrap: wrap;
}
.jop-setting-title {
margin: 10px 0 5px 0;
font-weight: 700;
}
.jop-setting-item {
display: flex;
height: 20px;
align-items: center;
margin-right: 15px;
user-select: none;
cursor: pointer;
}
/* */
/* db */
.db-panel .movie-panel-info div.panel-block {
padding: 5.5px 12px;
}
.db-panel .jop-app {
padding: 15px 12px;
}
/* */
/* lib */
.lib-panel .jop-app {
padding: 20px 30px;
margin-top: 10px;
}
/* */
/* */
input[type="checkbox"],
input[type="radio"] {
margin: 0 0 0 5px;
cursor: pointer;
}
/* */
.jop-tooltip-container {
position: relative;
display: inline-block;
}
.jop-tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
}
.jop-setting-label {
cursor: pointer;
}
.jop-checkbox {
display: inline-flex;
align-items: center;
cursor: pointer;
margin-right: 15px;
user-select: none;
}
.jop-checkbox-input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.jop-checkbox-custom {
position: relative;
display: inline-block;
width: 16px;
height: 16px;
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 2px;
transition: all 0.3s;
}
.jop-checkbox-input:checked + .jop-checkbox-custom {
background-color: #409eff;
border-color: #409eff;
}
.jop-checkbox-input:checked + .jop-checkbox-custom::after {
content: "";
position: absolute;
top: 1px;
left: 4px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.jop-checkbox-label {
margin-left: 3px;
font-size: 14px;
color: #606266;
}
.jop-checkbox:hover .jop-checkbox-custom {
border-color: #409eff;
}

View File

@@ -1,17 +0,0 @@
import { Infos } from "../components/Info";
import { Cms } from "./matchList";
export function getInfos(cms: Cms): Infos {
const codeNode = document.querySelector<HTMLElement>(cms.codeQueryStr) as HTMLElement;
const actorNodeList = document.querySelectorAll<HTMLAnchorElement>(cms.actorQueryStr);
const actorList = [...actorNodeList].map((item) => {
return { text: item.innerHTML, link: item.href };
});
return {
codeText:
cms.name === "javdb"
? (codeNode?.dataset.clipboardText as string)
: codeNode.innerText.replace("复制", ""),
actorList,
};
}

88
src/utils/index.ts Normal file
View File

@@ -0,0 +1,88 @@
import { GM_xmlhttpRequest } from "$";
import { type LibItem } from "./libSites";
import { SP_PREFIX } from "./siteList";
interface TResponse {
readonly responseHeaders: string;
readonly readyState: 0 | 1 | 2 | 3 | 4;
readonly response: any;
readonly responseText: string;
readonly responseXML: Document | null;
readonly status: number;
readonly statusText: string;
readonly finalUrl: string;
}
export const gmGet = ({ url }: { url: string }): Promise<TResponse> => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
onload: (response) => resolve(response),
onerror: (error) => reject(error),
});
});
};
export const gmPost = ({
url,
data,
}: {
url: string;
data?: Record<string, any>;
}): Promise<TResponse> => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
data: new URLSearchParams(data).toString(),
headers: { "Content-Type": "application/x-www-form-urlencoded" },
url,
onload: (response) => resolve(response),
onerror: (error) => reject(error),
});
});
};
export const isCaseInsensitiveEqual = (str1?: any, str2?: any) => {
if (!str1 || !str2) return false;
return str1.toLowerCase() === str2.toLowerCase();
};
export const isErrorCode = (resCode: number) => {
return [404, 403].includes(resCode);
};
export const getCode = (libItem: LibItem): string => {
const { codeQueryStr } = libItem.querys;
const codeNode = document.querySelector<HTMLElement>(codeQueryStr);
if (!codeNode) return "";
const codeText =
libItem.name === "javdb"
? (codeNode.dataset.clipboardText as string)
: codeNode.innerText.replace("复制", "");
if (codeText.includes("FC2")) return codeText.split("-")[1];
if (codeText.startsWith(SP_PREFIX)) return codeText.substring(3);
return codeText;
};
export const regEnum = {
subtitle: /(中文|字幕|subtitle)/,
leakage: /(无码|無碼|泄漏|泄露|Uncensored)/,
};
export const tagsQuery = ({
leakageText,
subtitleText,
}: {
leakageText: string;
subtitleText: string;
}) => {
const hasLeakage = regEnum.leakage.test(leakageText);
const hasSubtitle = regEnum.subtitle.test(subtitleText);
const tags = [];
if (hasLeakage) tags.push("无码");
if (hasSubtitle) tags.push("字幕");
return tags.join(" ");
};

58
src/utils/libSites.ts Normal file
View File

@@ -0,0 +1,58 @@
/** 当前 macth 站点对象 */
export type LibItem = {
name: "javdb" | "javbus" | "javlib";
enable: boolean;
identifier: string;
querys: {
panelQueryStr: string;
codeQueryStr: string;
};
method: () => void;
};
/** 需要匹配的图书馆站点列表 */
export const libSites: LibItem[] = [
{
name: "javdb",
enable: true,
identifier: "a[href*='javdb']",
querys: {
panelQueryStr: ".video-meta-panel>.columns.is-desktop .panel.movie-panel-info",
codeQueryStr: `[data-clipboard-text]`,
},
method() {
// 一些样式调整
const columnVideoCover = document.querySelector<HTMLElement>(".column-video-cover");
if (columnVideoCover) {
columnVideoCover.style.width = "60%";
}
const panel = document.querySelector<HTMLElement>(
".video-meta-panel>.columns.is-desktop>.column:not(.column-video-cover)",
);
panel?.classList.add("db-panel");
},
},
{
name: "javbus",
enable: true,
identifier: "a[href*='javbus']",
querys: {
panelQueryStr: ".movie>div.info",
codeQueryStr: `span[style="color:#CC0000;"]`,
},
method() {},
},
{
name: "javlib",
enable: true,
identifier: "img[src*='logo-top']",
querys: {
panelQueryStr: "#video_jacket_info #video_info",
codeQueryStr: `#video_id td.text`,
},
method() {
const panel = document.querySelector<HTMLElement>("#video_info");
panel?.classList.add("lib-panel");
},
},
];

View File

@@ -1,57 +0,0 @@
/** 当前 macth 站点对象 */
export type Cms = {
name: "javdb" | "javbus" | "javlib";
enable: boolean;
href: RegExp;
panelParentQueryStr: string;
codeQueryStr: string;
actorQueryStr: string;
method: () => void;
};
export const matchList: Cms[] = [
{
name: "javdb",
enable: true,
href: /^https:\/\/(\w*\.)?javdb(\d)*\.com.*$/,
panelParentQueryStr: ".video-meta-panel>.columns.is-desktop>.column:not(.column-video-cover)",
codeQueryStr: `[data-clipboard-text]`,
actorQueryStr: `span.value>a[href^="/actors"]`,
method() {},
},
{
name: "javbus",
enable: true,
// hostname: [
// "www.javbus.com",
// "www.seejav.one",
// "www.seejav.cc",
// "www.javsee.me",
// "www.javsee.in",
// ],
href: /^https?:\/\/(\w*\.)?(javbus|seejav|javsee)*\.(com|cc|me|life).*$/,
panelParentQueryStr: ".movie>div.info",
codeQueryStr: `span[style="color:#CC0000;"]`,
actorQueryStr: `.genre>a`,
method() {
// panel 加宽
const colmd8 = document.querySelector(".movie>.col-md-9.screencap");
colmd8?.classList.remove("col-md-9");
colmd8?.classList.add("col-md-8");
const colmd4 = document.querySelector(".movie>.col-md-3.info");
colmd4?.classList.remove("col-md-3");
colmd4?.classList.add("col-md-4");
},
},
{
name: "javlib",
enable: true,
// hostname: ["www.javlibrary.com", "www.javlib.com"],
href: /^https?:\/\/(\w*\.)?(javlib|javlibrary)*\.com.*$/,
panelParentQueryStr: "#video_jacket_info #video_info",
codeQueryStr: `#video_id td.text`,
actorQueryStr: `.cast>.star>a`,
method() {
// const infoPanel = document.querySelectorAll( `#video_jacket_info td[style="vertical-align: top;"]`, )[1]; infoPanel?.classList.add("JOPAPP");
},
},
];

View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,207 +1,327 @@
export interface DomQuery_parser {
/** 部分网站搜索页结果第一个是广告,所以要加一个 index */
listIndex?: number;
/** code 是用空格分割的 */
spaceCode?: boolean;
linkQuery: string;
titleQuery: string;
/** 确定返回颜色,暂未启用 */
certainColor?: string;
}
export interface DomQuery_get {
/** 收录视频,但是未提供在线播放资源 */
videoQuery?: string;
subQuery?: string;
leakQuery?: string;
}
interface SiteItemBase {
name: string;
/** 针对 matchList 的 hostname */
disable?: string;
hostname: string;
url: string;
codeFormater?: (arg0: string) => string;
method?: Function;
}
export interface SiteItem_get extends SiteItemBase {
fetcher: "get";
domQuery: DomQuery_get;
}
export interface SiteItem_parser extends SiteItemBase {
fetcher: "parser";
domQuery: DomQuery_parser;
}
export type SiteItem = SiteItem_get | SiteItem_parser;
const print = (name: string) => {
console.log(name);
};
/** 网站列表 */
export const siteList: SiteItem[] = [
{
name: "Jable",
hostname: "jable.tv",
url: "https://jable.tv/videos/{{code}}/",
fetcher: "get",
domQuery: { subQuery: ".header-right>h6" },
method: print,
},
{
name: "MISSAV",
hostname: "missav.com",
url: "https://missav.com/{{code}}/",
fetcher: "get",
domQuery: {
/** 标签区的第一个一般是字幕标签 */
subQuery: '.space-y-2 a.text-nord13[href="https://missav.com/chinese-subtitle"]',
/** videoPage 有个跳转按钮 */
leakQuery: ".order-first div.rounded-md a[href]:last-child",
},
method: print,
},
{
name: "NETFLAV",
hostname: "netflav.com",
url: "https://netflav.com/search?type=title&keyword={{code}}",
fetcher: "parser",
domQuery: { linkQuery: ".grid_cell>a", titleQuery: ".grid_cell>a>.grid_title" },
method: print,
},
{
name: "Avgle",
hostname: "avgle.com",
url: "https://avgle.com/search/videos?search_query={{code}}&search_type=videos",
fetcher: "parser",
domQuery: {
linkQuery: ".container>.row .row .well>a[href]",
titleQuery: ".container>.row .row .well .video-title",
},
method: print,
},
{
name: "JAVHHH",
hostname: "javhhh.com",
url: "https://javhhh.com/v/?wd={{code}}",
fetcher: "parser",
domQuery: {
linkQuery: ".typelist>.i-container>a[href]",
titleQuery: ".typelist>.i-container>a[href]",
},
method: print,
},
{
name: "BestJP",
hostname: "bestjavporn.com",
url: "https://www3.bestjavporn.com/search/{{code}}",
fetcher: "parser",
domQuery: { linkQuery: "article.thumb-block>a", titleQuery: "article.thumb-block>a" },
method: print,
},
{
name: "JAVMENU",
hostname: "javmenu.com",
url: "https://javmenu.com/{{code}}",
fetcher: "get",
domQuery: {
videoQuery: "a.nav-link[aria-controls='pills-0']",
},
method: print,
},
{
name: "Jav.Guru",
hostname: "jav.guru",
url: "https://jav.guru/?s={{code}}",
fetcher: "parser",
domQuery: { linkQuery: ".imgg>a[href]", titleQuery: ".inside-article>.grid1 a[title]" },
method: print,
},
{
name: "JAVMOST",
hostname: "javmost.cx",
url: "https://javmost.cx/search/{{code}}/",
fetcher: "parser",
domQuery: {
linkQuery: "#content .card a#MyImage",
titleQuery: "#content .card-block .card-title",
},
method: print,
},
{
name: "HAYAV",
hostname: "hayav.com",
url: "https://hayav.com/video/{{code}}/",
fetcher: "get",
domQuery: {},
method: print,
},
{
name: "JAVFC2",
hostname: "javfc2.net",
url: "https://javfc2.net/?s={{code}}",
fetcher: "parser",
domQuery: {
linkQuery: "article.loop-video>a[href]",
titleQuery: "article.loop-video .entry-header",
},
method: print,
},
{
name: "baihuse",
hostname: "paipancon.com",
url: "https://paipancon.com/search/{{code}}",
fetcher: "parser",
domQuery: {
linkQuery: "div.col>div.card>a[href]",
// 然而这个不是 title是图片这个站居然 title 里不包含 code反而图片包含
titleQuery: "div.card img.card-img-top",
},
method: print,
},
{
name: "GGJAV",
hostname: "ggjav.com",
url: "https://ggjav.com/main/search?string={{code}}",
fetcher: "parser",
domQuery: {
listIndex: 1,
spaceCode: true,
titleQuery: "div.columns.large-3.medium-6.small-12.item.float-left>div.item_title>a.gray_a",
linkQuery: "div.columns.large-3.medium-6.small-12.item.float-left>div.item_title>a.gray_a",
},
method: print,
},
{
name: "AV01",
hostname: "av01.tv",
url: "https://www.av01.tv/search/videos?search_query={{code}}",
fetcher: "parser",
domQuery: { linkQuery: "div[id].well-sm>a", titleQuery: ".video-views>.pull-left" },
method: print,
},
{
name: "JavBus",
disable: "javbus",
hostname: "javbus.com",
url: "https://javbus.com/{{code}}",
fetcher: "get",
domQuery: {},
method: print,
},
// {
// name: "JavDB",
// disable:"javdb",
// hostname: "javbus.com",
// url: "https://javbus.com/{{code}}",
// fetcher: "get",
// domQuery: {},
// method: print,
// },
];
export interface DomQuery_parser {
/** 部分网站搜索页结果第一个是广告,加一个 index,来指定到固定的位置。
* 点名GGJAV
*/
listIndex?: number;
/** 大部分 code 格式是 `xxx-000`,还有一部分用空格 `xxx 000`。
* 原来是点名 GGJAV 的
*/
spaceCode?: boolean;
/** a 标签 href 的 query */
linkQuery: string;
/** 在 title 里检测是否包含「字幕」等文本 */
titleQuery: string;
}
export interface DomQuery_get {
/** 检测是否提供播放
* JAVMENU 收录视频,但是未提供在线播放资源
*/
videoQuery?: string;
subQuery?: string;
leakQuery?: string;
}
interface SiteItemBase {
name: string;
/** [废弃] 用户定义的 disable */
// disable: boolean;
/** 在指定 LibItem.name 下不显示 */
hostname: string;
url: string;
/** 部分站 CODE 格式不一样 */
codeFormater?: (preCode: string) => string;
}
export interface SiteItem_get extends SiteItemBase {
fetchType: "get";
domQuery: DomQuery_get;
}
export interface SiteItem_parser extends SiteItemBase {
fetchType: "parser";
/** 严格匹配,会检查搜索结果的 code */
strictParser?: true;
domQuery: DomQuery_parser;
}
// export interface SiteItem_post extends SiteItemBase {
// fetchType: "post";
// postParams: Record<string, any>;
// domQuery: DomQuery_parser;
// }
export interface SiteItem_false extends SiteItemBase {
fetchType: "false";
}
export type SiteItem = SiteItem_get | SiteItem_parser | SiteItem_false;
/** 在线网站列表 */
export const siteList: SiteItem[] = [
{
name: "FANZA 動画",
hostname: "dmm.co.jp",
url: "https://www.dmm.co.jp/digital/videoa/-/detail/=/cid={{code}}/",
// url: "https://video.dmm.co.jp/av/list/?key={{code}}",
fetchType: "get",
codeFormater: (preCode) => {
const [pre, num] = preCode.split("-");
const padNum = num.padStart(5, "0");
if (pre.toLowerCase().startsWith("start")) {
return `1${pre.toLowerCase()}${padNum}`;
}
return `${pre}${padNum}`;
},
domQuery: {},
},
{
name: "Jable",
hostname: "jable.tv",
url: "https://jable.tv/videos/{{code}}/",
fetchType: "get",
domQuery: {
subQuery: ".info-header",
leakQuery: ".info-header",
},
},
{
name: "MISSAV",
hostname: "missav.ws",
url: "https://missav.ws/{{code}}/",
fetchType: "get",
domQuery: {
// 标签区的第一个一般是字幕标签
subQuery: '.space-y-2 a.text-nord13[href="https://missav.ws/chinese-subtitle"]',
// 有个「切換無碼」按钮,藏在分享按钮旁边……
leakQuery: ".order-first div.rounded-md a[href]:last-child",
},
},
{
name: "123av",
hostname: "123av.com",
url: "https://123av.com/zh/search?keyword={{code}}",
fetchType: "parser",
strictParser: true,
domQuery: {
linkQuery: `.detail>a[href*='v/']`,
titleQuery: `.detail>a[href*='v/']`,
},
},
{
// 有可能搜出仨leakage subtitle 4k
name: "Supjav",
hostname: "supjav.com",
url: "https://supjav.com/zh/?s={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: `.posts.clearfix>.post>a.img[title]`,
titleQuery: `h3>a[rel="bookmark"][itemprop="url"]`,
},
},
{
name: "NETFLAV",
hostname: "netflav5.com",
url: "https://netflav5.com/search?type=title&keyword={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: ".grid_0_cell>a[href^='/video?']",
titleQuery: ".grid_0_cell>a[href^='/video?'] .grid_0_title",
},
},
{
name: "Avgle",
hostname: "avgle.com",
url: "https://avgle.com/search/videos?search_query={{code}}&search_type=videos",
fetchType: "parser",
domQuery: {
linkQuery: ".container>.row .row .well>a[href]",
titleQuery: ".container>.row .row .well .video-title",
},
},
{
name: "JAVHHH",
hostname: "javhhh.com",
url: "https://javhhh.com/v/?wd={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: ".typelist>.i-container>a[href]",
titleQuery: ".typelist>.i-container>a[href]",
},
},
{
name: "BestJP",
hostname: "bestjavporn.com",
url: "https://www3.bestjavporn.com/search/{{code}}",
fetchType: "parser",
domQuery: { linkQuery: "article.thumb-block>a", titleQuery: "article.thumb-block>a" },
},
{
name: "JAVMENU",
hostname: "javmenu.com",
url: "https://javmenu.com/{{code}}",
fetchType: "get",
domQuery: {
videoQuery: "a.nav-link[aria-controls='pills-0']",
},
// codeFormater: (preCode) => preCode.replace("-", ""),
},
{
name: "Jav.Guru",
hostname: "jav.guru",
url: "https://jav.guru/?s={{code}}",
fetchType: "parser",
domQuery: { linkQuery: ".imgg>a[href]", titleQuery: ".inside-article>.grid1 a[title]" },
},
{
name: "JAVMOST",
hostname: "javmost.cx",
url: "https://javmost.cx/search/{{code}}/",
fetchType: "parser",
domQuery: {
linkQuery: ".card #myButton",
titleQuery: ".card-block h4.card-title",
},
},
{
name: "HAYAV",
hostname: "hayav.com",
url: "https://hayav.com/video/{{code}}/",
fetchType: "get",
domQuery: {
// subQuery: `.site__col>.entry-header>h1.entry-title`,
},
},
{
name: "AvJoy",
hostname: "avjoy.me",
url: "https://avjoy.me/search/videos/{{code}}",
fetchType: "parser",
domQuery: {
titleQuery: `#wrapper .row .content-info span.content-title`,
linkQuery: `#wrapper .row a[href^="/video/"]`,
},
},
{
name: "JAVFC2",
hostname: "javfc2.net",
url: "https://javfc2.net/?s={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: "article.loop-video>a[href]",
titleQuery: "article.loop-video .entry-header",
},
},
{
name: "baihuse",
hostname: "paipancon.com",
url: "https://paipancon.com/search/{{code}}",
fetchType: "parser",
domQuery: {
linkQuery: "div.col>div.card>a[href]",
// 然而这个不是 title是图片这个站居然 title 里不包含 code反而图片包含
titleQuery: "div.card img.card-img-top",
},
},
{
name: "GGJAV",
hostname: "ggjav.com",
url: "https://ggjav.com/main/search?string={{code}}",
fetchType: "parser",
domQuery: {
listIndex: 1,
// spaceCode: true,
titleQuery: "div.columns.large-3.medium-6.small-12.item.float-left>div.item_title>a.gray_a",
linkQuery: "div.columns.large-3.medium-6.small-12.item.float-left>div.item_title>a.gray_a",
},
},
{
name: "AV01",
hostname: "www.av01.tv",
url: "https://www.av01.tv/search/videos?search_query={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: "div.well>a[href^='/video/']",
titleQuery: "div.well>a[href^='/video/']",
},
},
{
name: "18sex",
hostname: "18sex.org",
url: "https://www.18sex.org/cn/search/{{code}}/",
fetchType: "parser",
domQuery: { linkQuery: ".white_link[href]", titleQuery: ".white_link>.card-title" },
},
{
name: "highporn",
hostname: "highporn.net",
url: "https://highporn.net/search/videos?search_query={{code}}",
fetchType: "parser",
domQuery: { linkQuery: ".well>a[href]", titleQuery: ".well>a[href]>span.video-title" },
},
{
// 套了个 cf_clearance 的 cookie不好搞
name: "evojav",
hostname: "evojav.pro",
url: "https://evojav.pro/video/{{code}}/",
fetchType: "get",
domQuery: {},
},
{
name: "18av",
hostname: "18av.mm-cg.com",
url: "https://18av.mm-cg.com/zh/fc_search/all/{{code}}/1.html",
fetchType: "parser",
domQuery: { linkQuery: ".posts h3>a[href]", titleQuery: ".posts h3>a[href]" },
},
{
name: "javgo",
hostname: "javgo.to",
url: "https://javgo.to/zh/v/{{code}}",
fetchType: "get",
domQuery: {},
},
{
name: "javhub",
hostname: "javhub.net",
url: "https://javhub.net/search/{{code}}",
fetchType: "parser",
domQuery: { linkQuery: "a.card-text[href*='play']", titleQuery: "a.card-text[href*='play']" },
},
{
name: "JavBus",
hostname: "javbus.com",
url: "https://javbus.com/{{code}}",
fetchType: "get",
domQuery: {},
codeFormater: (preCode) => (preCode.startsWith("MIUM") ? `${SP_PREFIX}${preCode}` : preCode),
},
{
name: "JavDB",
hostname: "javdb.com",
url: "https://javdb.com/search?q={{code}}",
fetchType: "parser",
domQuery: {
linkQuery: ".movie-list>.item:first-child>a",
titleQuery: ".video-title",
},
},
{
name: "JAVLib",
hostname: "javlibrary.com",
url: "https://www.javlibrary.com/cn/vl_searchbyid.php?keyword={{code}}",
fetchType: "false",
// domQuery: {
// linkQuery: ".videothumblist .video[id]:first-child>a",
// titleQuery: ".videothumblist .video[id]:first-child>a>div.id",
// },
},
];
/** bus 里有些以 '300MIUM' 开头,要处理掉这个 300 */
export const SP_PREFIX = "300" as const;

View File

@@ -1,127 +1,137 @@
import { GM_xmlhttpRequest } from "$";
import { RenderSiteItem } from "../components/App";
import type { DomQuery_get, DomQuery_parser, SiteItem } from "./siteList";
export type xhrResult = {
isSuccess: boolean;
targetLink: string;
hasSubtitle: boolean;
hasLeakage: boolean;
msg: string;
};
/** 针对视频播放页进行解析,寻找字幕等信息 */
function videoPageParser(responseText: string, { subQuery, leakQuery, videoQuery }: DomQuery_get) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
const subNode = subQuery ? doc.querySelector<HTMLElement>(subQuery) : "";
const subNodeText = subNode ? subNode.innerHTML : "";
const leakNode = leakQuery ? doc.querySelector<HTMLElement>(leakQuery) : null;
// 部分网站收录视频,但是未提供播放资源,所以需要使用 videoQuery 进一步检测是否存在在线播放
/** videoQuery 为 undefine 时,不需要查找 video */
const videoNode = videoQuery ? doc.querySelector<HTMLElement>(videoQuery) : true;
return {
isSuccess: !!videoNode,
hasSubtitle: subNodeText.includes("字幕") || subNodeText.includes("subtitle"),
hasLeakage: !!leakNode,
};
}
/** 针对 fetcher==="parser" 时的搜索结果页进行解析,寻找是否存在视频资源。
* linkQuery、titleQuery 都是必须,
* linkQuery 有结果且 titleQuery 结果包含 code返回 isSuccess。
* 再检查下 title 中是否含有字幕信息等
*/
function serachPageParser(
responseText: string,
{ linkQuery, titleQuery, listIndex = 0, spaceCode = false }: DomQuery_parser,
siteHostName: string,
CODE: string,
) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
const linkNode = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery)[listIndex] : null;
const titleNode = titleQuery ? doc.querySelectorAll(titleQuery)[listIndex] : null;
const titleNodeText = titleNode ? titleNode?.outerHTML : "";
function query() {
/** 空格版本的 code */
const envCodeWithSpace = spaceCode ? CODE.replace("-", " ") : CODE;
const condition =
linkNode &&
titleNode &&
(titleNodeText.includes(envCodeWithSpace) || titleNodeText.includes(CODE));
if (condition) {
return {
isSuccess: true,
targetLink: linkNode.href.replace(linkNode.hostname, siteHostName),
hasLeakage: titleNodeText.includes("无码") || titleNodeText.includes("Uncensored"),
hasSubtitle: titleNodeText.includes("字幕") || titleNodeText.includes("subtitle"),
};
} else {
return { targetLink: "", isSuccess: false, hasSubtitle: false, hasLeakage: false };
}
}
return query();
}
async function xhr(siteItem: SiteItem, targetLink: string, CODE: string) {
const xhrPromise: Promise<xhrResult> = new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: targetLink,
onload: (response) => {
if (siteItem.fetcher === "get") {
// 直接 get 网页,且 get 结果为 404大概是对应网站没有资源
if (response.status === 404) {
resolve({
isSuccess: false,
targetLink,
hasSubtitle: false,
hasLeakage: false,
msg: "应该是没有资源",
});
}
// 直接 get 网页,成功,需要进一步解析 videoPage获取字幕等信息
else {
const { hasSubtitle, hasLeakage, isSuccess } = videoPageParser(
response.responseText,
siteItem.domQuery,
);
resolve({ isSuccess, targetLink, hasSubtitle, hasLeakage, msg: "[get],存在资源" });
}
}
// 需要解析 searchPage
else if (siteItem.fetcher === "parser") {
const { targetLink, isSuccess, hasLeakage, hasSubtitle } = serachPageParser(
response.responseText,
siteItem.domQuery,
siteItem.hostname,
CODE,
);
resolve({
isSuccess,
targetLink: isSuccess ? targetLink : targetLink,
hasSubtitle,
hasLeakage,
msg: "[parser]存在资源",
});
}
},
onerror: (error) => {
resolve({
isSuccess: false,
targetLink: targetLink,
hasSubtitle: false,
hasLeakage: false,
msg: error.error,
});
},
});
});
return xhrPromise;
}
export default xhr;
import { gmGet, isCaseInsensitiveEqual, isErrorCode, tagsQuery } from "./";
import type { DomQuery_get, SiteItem, SiteItem_get, SiteItem_parser } from "./siteList";
export type FetchResult = {
isSuccess: boolean;
resultLink?: string;
tag?: string;
multipResLink?: string;
multipleRes?: boolean;
};
/** 针对视频播放页进行解析,寻找字幕等信息 */
function videoPageParser(responseText: string, { subQuery, leakQuery, videoQuery }: DomQuery_get) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
const subNode = subQuery ? doc.querySelector<HTMLElement>(subQuery) : "";
const subNodeText = subNode ? subNode.innerHTML : "";
const leakNode = leakQuery ? doc.querySelector<HTMLElement>(leakQuery) : null;
const leakNodeText = leakNode ? leakNode.innerHTML : "";
/** 部分网站收录视频,但是未提供播放资源,所以需要使用 videoQuery 进一步检测是否存在在线播放
* videoQuery 为 undefine 时,不需要查找 video
*/
const videoNode = videoQuery ? doc.querySelector<HTMLElement>(videoQuery) : true;
return {
isSuccess: !!videoNode,
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" 时的搜索结果页进行解析,寻找是否存在视频资源。
* linkQuery & titleQuery 都是必须,
* linkQuery 有结果且 titleQuery 有结果包含 code返回 isSuccess。
* 再检查下 title 中是否含有字幕信息等
*/
function serachPageParser(responseText: string, siteItem: SiteItem_parser, CODE: string) {
const { linkQuery, titleQuery } = siteItem.domQuery;
const doc = new DOMParser().parseFromString(responseText, "text/html");
const titleNodes = titleQuery ? doc.querySelectorAll(titleQuery) : [];
const { isSuccess, titleNodeText, multipleRes } = searchPageCodeCheck(titleNodes, siteItem, CODE);
const linkNodes = linkQuery ? doc.querySelectorAll<HTMLAnchorElement>(linkQuery) : [];
const linkNode = linkNodes[siteItem.domQuery.listIndex ?? 0];
if (!isSuccess) {
return { isSuccess: false };
}
const resultLinkText = linkNode.href.replace(linkNode.hostname, siteItem.hostname);
return {
isSuccess: true,
resultLink: resultLinkText,
multipleRes,
tag: tagsQuery({ leakageText: titleNodeText, subtitleText: titleNodeText }),
};
}
type Args = {
siteItem: SiteItem;
targetLink: string;
CODE: string;
};
const baseFetcher = async ({ siteItem, targetLink, CODE }: Args): Promise<FetchResult> => {
if (siteItem.fetchType === "false") {
return Promise.resolve({
isSuccess: true,
resultLink: targetLink,
});
}
try {
const response = await gmGet({ url: targetLink });
if (isErrorCode(response.status)) {
// 请求 404大概是对应网站没有资源
throw Error(String(response.status));
}
if (siteItem.fetchType === "get") {
// 直接 get 网页,成功,需要进一步解析 videoPage获取字幕等信息
return {
resultLink: targetLink,
...videoPageParser(response.responseText, siteItem.domQuery),
};
} else {
return {
...serachPageParser(response.responseText, siteItem, CODE),
};
}
} catch (error) {
return {
isSuccess: false,
};
}
};
/** jable 有些域名是带 -c */
const javbleFetcher = async (args: Args): Promise<FetchResult> => {
const res = await baseFetcher(args);
if (res.isSuccess) return res;
const newLink = args.targetLink.slice(0, -1) + "-c/";
return await baseFetcher({ ...args, targetLink: newLink });
};
export const fetcher = (args: Args) => {
if (args.siteItem.name === "Jable") {
return javbleFetcher(args);
}
return baseFetcher(args);
};

View File

@@ -1,24 +1,23 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
},
"include": ["./src/**/*.ts"],
"exclude": ["src/buckup"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"baseUrl": "./",
"paths": { "@/*": ["./src/*"] }
},
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
"exclude": ["**/backup"]
}

View File

@@ -1,9 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "src/utils/siteList.ts"]
}
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,33 +1,33 @@
import { defineConfig } from "vite";
import monkey, { cdn } from "vite-plugin-monkey";
import monkey, { cdn, MonkeyUserScript } from "vite-plugin-monkey";
import preact from "@preact/preset-vite";
import type { MonkeyUserScript } from "vite-plugin-monkey";
import urlConfig from "./script/urlConfig";
import { siteList } from "./src/utils/siteList";
const connectList = siteList.map((site) => site.hostname);
// const PARALLEL =
// "https://raw.githubusercontent.com/Tampermonkey/utils/refs/heads/main/requires/gh_2215_make_GM_xhr_more_parallel_again.js";
const PARALLEL = "https://update.greasyfork.org/scripts/522123/1511104/tampermonkey%20parallel.js";
const UserscriptConfig: MonkeyUserScript = {
author: "mission522",
version: "1.0.4",
version: "1.2.11",
license: "MIT",
name: "JAV 添加跳转在线观看 三合一",
match: [
"*://*/cn/?v=jav*",
// "*://*.javdb.com/*", "*://*.javbus.com/*", "*://*.seejav.com/*", "*://*.seejav.cc/*", "*://*.javsee.com/*", "*://*.javlib.com/*", "*://*.javlibrary.com/*",
],
include: [
/^https?:\/\/(\w*\.)?javdb(\d)*\.com.*$/,
/^https?:\/\/(\w*\.)?(javbus|seejav|javsee)*\.(com|cc|me|life).*$/,
/^https?:\/\/(\w*\.)?(javlib|javlibrary)*\.com.*$/,
],
name: "JAV 添加跳转在线观看",
icon: "https://javdb.com/favicon-32x32.png",
namespace: "https://greasyfork.org/zh-CN/scripts/429173",
description:
"在 JavDB、JavBus、JavLibrart 网站的影片详情页添加跳转在线播放按钮,并在按钮上标注是否支持在线播放、包含无码或包含字幕",
connect: connectList,
description: "为 JavDB、JavBus、JavLibrary 这三个站点添加跳转在线观看的链接",
...urlConfig,
require: [PARALLEL],
};
export default defineConfig({
build: {
minify: false,
rollupOptions: {},
// target:''
},
resolve: { alias: { "@": "/src/" } },
esbuild: { charset: "utf8" },
plugins: [
preact(),
monkey({
@@ -38,7 +38,6 @@ export default defineConfig({
preact: cdn.jsdelivr("preact", "dist/preact.min.js"),
},
},
userscript: UserscriptConfig,
}),
],