- 新网站 -feat:自定义需要默认显示的网站

This commit is contained in:
mrbunker
2022-10-23 22:56:48 +08:00
parent 17febed302
commit 3a6da84e88
14 changed files with 346 additions and 113 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# **/buckup

123
dist/jop.user.js vendored
View File

@@ -1,7 +1,7 @@
// ==UserScript==
// @name JAV 添加跳转在线观看 三合一
// @namespace https://greasyfork.org/zh-CN/scripts/429173
// @version 1.1.0
// @version 1.1.1
// @author mission522
// @description 在 JavDB、JavBus、JavLibrary 网站的影片详情页添加跳转在线播放按钮,并在按钮上标注是否支持在线播放、包含无码或包含字幕
// @license MIT
@@ -13,6 +13,7 @@
// @require https://cdn.jsdelivr.net/npm/preact@10.11.0/dist/preact.min.js
// @connect jable.tv
// @connect missav.com
// @connect supjav.com
// @connect netflav.com
// @connect avgle.com
// @connect javhhh.com
@@ -21,6 +22,7 @@
// @connect jav.guru
// @connect javmost.cx
// @connect hayav.com
// @connect avjoy.me
// @connect javfc2.net
// @connect paipancon.com
// @connect ggjav.com
@@ -28,10 +30,11 @@
// @connect javbus.com
// @connect javdb005.com
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(e=>{const o=document.createElement("style");o.dataset.source="vite-plugin-monkey",o.innerText=e,document.head.appendChild(o)})(".jopApp{box-sizing:border-box;display:flex;flex-wrap:wrap;justify-content:flex-start;gap:10px;width:100%;height:100%;z-index:1;background-color:#fff;transition:right .2s ease-in-out;font-family:Roboto,Helvetica,Arial,sans-serif;color:#000}.jop-button{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}.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:.75;color:#67c23a;border:1px solid #e1f3d8;background:white}.jop-button_green{color:#fff!important;background-color:#67c23a}.jop-button_green:hover{color:#fff!important;background-color:#95d475}.jop-button_red{color:#fff!important;background-color:#f56c6c}.jop-button_red:hover{color:#fff!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)}to{transform:rotate(360deg)}}.jop-tag{padding:3px 6px;color:#409eff!important;background:#ecf5ff;border:1px solid #d9ecff;border-radius:4px}.db-panel .movie-panel-info div.panel-block{padding:5.5px 12px}.db-panel .jopApp{padding:15px 12px}.lib-panel .jopApp{padding:20px 30px}");
(t=>{const o=document.createElement("style");o.dataset.source="vite-plugin-monkey",o.innerText=t,document.head.appendChild(o)})(".jop-list{box-sizing:border-box;display:flex;flex-wrap:wrap;justify-content:flex-start;gap:10px;width:100%;height:100%;z-index:1;background-color:#fff;transition:right .2s ease-in-out;font-family:Roboto,Helvetica,Arial,sans-serif;color:#000}.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:.75;color:#67c23a;border:1px solid #e1f3d8;background:white}.jop-button_green{color:#fff!important;background-color:#67c23a}.jop-button_green:hover{color:#fff!important;background-color:#95d475}.jop-button_red{color:#fff!important;background-color:#f56c6c}.jop-button_red:hover{color:#fff!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)}to{transform:rotate(360deg)}}.jop-tag{padding:3px 6px;color:#409eff!important;background:#ecf5ff;border:1px solid #d9ecff;border-radius:4px}.jop-setting-list{display:flex;flex-wrap:wrap;background-color:#fff}.jop-setting-title{margin:10px 0 5px}.jop-setting-item{display:flex;height:20px;justify-content:center;align-items:center;margin-right:15px}.db-panel .movie-panel-info div.panel-block{padding:5.5px 12px}.db-panel .jop-app{padding:15px 12px}.lib-panel .jop-app{padding:20px 30px;margin-top:10px}input[type=checkbox],input[type=radio]{margin:0 0 0 5px}");
(function(preact2, client) {
"use strict";
@@ -396,6 +399,18 @@
},
method: print
},
{
name: "Supjav",
disable: false,
hostname: "supjav.com",
url: "https://supjav.com/zh/?s={{code}}",
fetcher: "parser",
domQuery: {
linkQuery: `.posts.clearfix>.post>a.img[title]`,
titleQuery: `h3>a[rel="bookmark"][itemprop="url"]`
},
method: print
},
{
name: "NETFLAV",
disable: false,
@@ -488,6 +503,18 @@
domQuery: {},
method: print
},
{
name: "AvJoy",
disable: false,
hostname: "avjoy.me",
url: "https://avjoy.me/search/video/{{code}}",
fetcher: "parser",
domQuery: {
titleQuery: `.content-info>.content-title`,
linkQuery: `.content-row>a`
},
method: print
},
{
name: "JAVFC2",
disable: false,
@@ -676,8 +703,9 @@
isSuccess: "pedding",
hasSubtitle: false,
hasLeakage: false,
targetLink: siteItem.url.replace("{{code}}", CODE)
targetLink: ""
});
const link = siteItem.url.replace("{{code}}", CODE);
const {
isSuccess,
hasSubtitle,
@@ -685,20 +713,20 @@
targetLink
} = status;
s(() => {
xhr(siteItem, targetLink, CODE).then((res) => {
xhr(siteItem, link, CODE).then((res) => {
setStatus({
isSuccess: res.isSuccess ? "fulfilled" : "rejected",
hasSubtitle: res.hasSubtitle,
hasLeakage: res.hasLeakage,
targetLink
targetLink: res.targetLink
});
});
}, [xhr, siteItem, CODE, targetLink]);
}, [xhr, siteItem, CODE, link]);
const colorClass = isSuccess === "pedding" ? " " : isSuccess === "fulfilled" ? "jop-button_green " : "jop-button_red ";
return o("a", {
className: "jop-button " + colorClass,
target: "_blank",
href: targetLink,
href: targetLink === "" ? link : targetLink,
children: [(hasSubtitle || hasLeakage) && o("div", {
className: "jop-button_label",
children: [hasSubtitle && o("span", {
@@ -711,17 +739,84 @@
})]
});
});
const Setting = ({
sites,
setSites,
disable
}) => {
const [showSetting, setShowSetting] = y(false);
return o(preact2.Fragment, {
children: [!showSetting ? o("div", {
className: "jop-button_def",
onClick: (e2) => {
setShowSetting(!showSetting);
},
children: "\u8BBE\u7F6E"
}) : o("h4", {
className: "jop-setting-title",
children: "\u52FE\u9009\u9ED8\u8BA4\u663E\u793A\u7684\u7F51\u7AD9"
}), showSetting && o(preact2.Fragment, {
children: [o("div", {
className: "jop-setting",
children: o("div", {
className: "jop-setting-list",
children: sites.map((item, index) => o("div", {
className: "jop-setting-item",
children: [item.name, o("input", {
type: "checkbox",
className: "jop-setting-checkbox",
checked: !disable.includes(item.name),
onChange: (e2) => {
var _a;
const checked = (_a = e2.target) == null ? void 0 : _a.checked;
sites[index].disable = !checked;
}
})]
}))
})
}), o("div", {
className: "jop-button_def",
onClick: (e2) => {
setShowSetting(!showSetting);
const newDisable = sites.map((item) => {
if (item.disable)
return item.name;
});
client.GM_setValue("disable", newDisable);
setSites([...sites]);
},
children: "\u4FDD\u5B58"
})]
})]
});
};
const App = w(function({
current,
CODE
}) {
const gmSiteList = client.GM_getValue("gmSiteList", siteList);
const siteListFilter = gmSiteList.filter((item) => item.disableHostname !== current.name && !item.disable);
const disable = client.GM_getValue("disable", ["AvJoy", "baihuse", "AV01"]);
const [sites, setSites] = y(siteList);
const siteListFilter = sites.filter((item) => item.disableHostname !== current.name && !item.disable);
let filter = [];
disable.forEach((disItem) => {
filter = siteListFilter.filter((jtem) => {
return disItem !== jtem.name;
});
});
return o(preact2.Fragment, {
children: siteListFilter.map((item) => o(SiteButton, {
siteItem: item,
CODE
}))
children: [o("div", {
class: "jop-list",
children: filter.map((item) => o(SiteButton, {
siteItem: item,
CODE
}))
}), o("div", {
children: o(Setting, {
sites,
setSites,
disable
})
})]
});
});
function main() {
@@ -734,7 +829,7 @@
if (panel === null)
return;
const app = document.createElement("div");
app.classList.add("jopApp");
app.classList.add("jop-app");
panel.append(app);
preact2.render(o(App, {
current,

View File

@@ -1,31 +1,36 @@
import { memo } from "preact/compat";
import { siteList } from "@/utils/siteList";
import { memo, useState } from "preact/compat";
import { SiteItem, siteList } from "@/utils/siteList";
import { GM_getValue } from "$";
import type { Current } from "@/utils/matchList";
import SiteButton from "./SiteButton";
export type RenderSiteItem = {
name: string;
targetLink: string;
status: {
isSuccess: "pedding" | "rejected" | "fulfilled";
hasSubtitle: boolean;
hasLeakage: boolean;
};
};
import { Setting } from "./Setting";
const App = memo(function ({ current, CODE }: { current: Current; CODE: string }) {
const gmSiteList = GM_getValue("gmSiteList", siteList);
/** 禁用 disable */
const siteListFilter = gmSiteList.filter(
const disable = GM_getValue<SiteItem["name"][]>("disable", ["AvJoy", "baihuse", "AV01"]);
// sites 最原始的 siteList.json
const [sites, setSites] = useState(siteList);
/** 禁用 hostname */
const siteListFilter = sites.filter(
(item) => item.disableHostname !== current.name && !item.disable,
);
let filter: SiteItem[] = [];
disable.forEach((disItem) => {
filter = siteListFilter.filter((jtem) => {
return disItem !== jtem.name;
});
});
return (
<>
{siteListFilter.map((item) => (
<SiteButton siteItem={item} CODE={CODE} />
))}
<div class="jop-list">
{filter.map((item) => (
<SiteButton siteItem={item} CODE={CODE} />
))}
</div>
<div>
<Setting sites={sites} setSites={setSites} disable={disable} />
</div>
</>
);
});

View File

@@ -0,0 +1,66 @@
import { SiteItem } from "@/utils/siteList";
import { StateUpdater, useState } from "preact/hooks";
import { GM_setValue } from "vite-plugin-monkey/dist/client";
export const Setting = ({
sites,
setSites,
disable,
}: {
sites: SiteItem[];
setSites: StateUpdater<SiteItem[]>;
disable: SiteItem["name"][];
}) => {
const [showSetting, setShowSetting] = useState(false);
return (
<>
{!showSetting ? (
<div
className="jop-button_def"
onClick={(e) => {
setShowSetting(!showSetting);
}}
>
</div>
) : (
<h4 className="jop-setting-title"></h4>
)}
{showSetting && (
<>
<div className="jop-setting">
<div className="jop-setting-list">
{sites.map((item, index) => (
<div className="jop-setting-item">
{item.name}
<input
type="checkbox"
className="jop-setting-checkbox"
checked={!disable.includes(item.name)}
onChange={(e: any) => {
const checked: boolean = e.target?.checked;
sites[index].disable = !checked;
}}
/>
</div>
))}
</div>
</div>
<div
className="jop-button_def"
onClick={(e) => {
setShowSetting(!showSetting);
const newDisable = sites.map((item) => {
if (item.disable) return item.name;
});
GM_setValue("disable", newDisable);
setSites([...sites]);
}}
>
</div>
</>
)}
</>
);
};

View File

@@ -1,7 +1,6 @@
import { SiteItem } from "@/utils/siteList";
import xhr from "@/utils/xhr";
import { memo, useEffect, useState } from "preact/compat";
interface Status {
isSuccess: "pedding" | "rejected" | "fulfilled";
hasSubtitle: boolean;
@@ -14,20 +13,21 @@ const SiteButton = memo(({ siteItem, CODE }: { siteItem: SiteItem; CODE: string
isSuccess: "pedding",
hasSubtitle: false,
hasLeakage: false,
targetLink: siteItem.url.replace("{{code}}", CODE),
targetLink: "",
});
const link = siteItem.url.replace("{{code}}", CODE);
const { isSuccess, hasSubtitle, hasLeakage, targetLink } = status;
useEffect(() => {
xhr(siteItem, targetLink, CODE).then((res) => {
xhr(siteItem, link, CODE).then((res) => {
setStatus({
isSuccess: res.isSuccess ? "fulfilled" : "rejected",
hasSubtitle: res.hasSubtitle,
hasLeakage: res.hasLeakage,
targetLink,
targetLink: res.targetLink,
});
});
}, [xhr, siteItem, CODE, targetLink]);
}, [xhr, siteItem, CODE, link]);
const colorClass =
isSuccess === "pedding"
? " "
@@ -36,7 +36,11 @@ const SiteButton = memo(({ siteItem, CODE }: { siteItem: SiteItem; CODE: string
: "jop-button_red ";
return (
<a className={"jop-button " + colorClass} target="_blank" href={targetLink}>
<a
className={"jop-button " + colorClass}
target="_blank"
href={targetLink === "" ? link : targetLink}
>
{(hasSubtitle || hasLeakage) && (
<div className="jop-button_label">
{hasSubtitle && <span> </span>}

View File

@@ -1,6 +1,6 @@
import { StateUpdater } from "preact/hooks";
import { GM_setValue } from "vite-plugin-monkey/dist/client";
import { RenderSiteItem } from "./App";
import { RenderSiteItem } from "../App";
export const ListSetting = ({
siteLists,

View File

@@ -1,6 +1,6 @@
import { GM_getValue, GM_setValue } from "$";
import { StateUpdater, useState } from "preact/hooks";
import { RenderSiteItem } from "./App";
import { RenderSiteItem } from "../App";
import { ListSetting } from "./ListSetting";
const Top = ({

View File

@@ -21,7 +21,7 @@ function main() {
if (panel === null) return;
const app = document.createElement("div");
app.classList.add("jopApp");
app.classList.add("jop-app");
panel.append(app);
render(<App current={current} CODE={CODE} />, app);

View File

@@ -1,5 +1,5 @@
/* */
.jopApp {
.jop-list {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
@@ -15,8 +15,8 @@
}
/* */
.jop-button {
.jop-button,
.jop-button_def {
position: relative;
display: flex;
align-items: center;
@@ -29,6 +29,11 @@
font-size: 14px;
border: 1px solid #dcdfe6;
color: #606266;
cursor: pointer;
}
.jop-button_def {
margin: 10px 0;
width: 100px;
}
.jop-button:visited {
color: #606266;
@@ -101,17 +106,44 @@
border-radius: 4px;
}
/* */
.jop-setting-list {
display: flex;
flex-wrap: wrap;
background-color: white;
}
.jop-setting-title {
margin: 10px 0 5px 0;
}
.jop-setting-item {
display: flex;
height: 20px;
justify-content: center;
align-items: center;
margin-right: 15px;
}
/* */
/* db */
.db-panel .movie-panel-info div.panel-block {
padding: 5.5px 12px;
}
.db-panel .jopApp {
.db-panel .jop-app {
padding: 15px 12px;
}
/* */
/* lib */
.lib-panel .jopApp {
.lib-panel .jop-app {
padding: 20px 30px;
margin-top: 10px;
}
/* */
/* */
input[type="checkbox"],
input[type="radio"] {
margin: 0 0 0 5px;
}
/* */

View File

@@ -5,8 +5,6 @@ export interface DomQuery_parser {
spaceCode?: boolean;
linkQuery: string;
titleQuery: string;
/** 确定返回颜色,暂未启用 */
certainColor?: string;
}
export interface DomQuery_get {
@@ -18,8 +16,9 @@ export interface DomQuery_get {
interface SiteItemBase {
name: string;
/** 针对 matchList 的 hostname */
/** 用户定义的 disable */
disable: boolean;
/** 针对 matchList 的 hostname */
disableHostname?: string;
hostname: string;
url: string;
@@ -68,13 +67,29 @@ export const siteList: SiteItem[] = [
},
method: print,
},
{
// 有可能搜出仨leakage subtitle 4k
name: "Supjav",
disable: false,
hostname: "supjav.com",
url: "https://supjav.com/zh/?s={{code}}",
fetcher: "parser",
domQuery: {
linkQuery: `.posts.clearfix>.post>a.img[title]`,
titleQuery: `h3>a[rel="bookmark"][itemprop="url"]`,
},
method: print,
},
{
name: "NETFLAV",
disable: false,
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" },
domQuery: {
linkQuery: ".grid_cell>a",
titleQuery: ".grid_cell>a>.grid_title",
},
method: print,
},
{
@@ -148,10 +163,23 @@ export const siteList: SiteItem[] = [
hostname: "hayav.com",
url: "https://hayav.com/video/{{code}}/",
fetcher: "get",
domQuery: {},
domQuery: {
// subQuery: `.site__col>.entry-header>h1.entry-title`,
},
method: print,
},
{
name: "AvJoy",
disable: false,
hostname: "avjoy.me",
url: "https://avjoy.me/search/video/{{code}}",
fetcher: "parser",
domQuery: {
titleQuery: `.content-info>.content-title`,
linkQuery: `.content-row>a`,
},
method: print,
},
{
name: "JAVFC2",
disable: false,

View File

@@ -19,7 +19,6 @@ function videoPageParser(responseText: string, { subQuery, leakQuery, videoQuery
// 部分网站收录视频,但是未提供播放资源,所以需要使用 videoQuery 进一步检测是否存在在线播放
/** videoQuery 为 undefine 时,不需要查找 video */
const videoNode = videoQuery ? doc.querySelector<HTMLElement>(videoQuery) : true;
return {
isSuccess: !!videoNode,
hasSubtitle: subNodeText.includes("字幕") || subNodeText.includes("subtitle"),
@@ -122,67 +121,68 @@ async function xhr(siteItem: SiteItem, targetLink: string, CODE: string) {
});
return xhrPromise;
}
export default xhr;
/** 获取 javdb 的分数
* 没用了,白写
*/
export function getDbScore(url: string): Promise<string> {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
onload: (response) => {
const doc = new DOMParser().parseFromString(response.responseText, "text/html");
const plist = doc.querySelector<HTMLElement>(`.panel.movie-panel-info`);
const innerHtml = plist?.innerHTML;
const matchResult = innerHtml?.match(/\d\.\d分/);
if (!innerHtml || !matchResult) {
reject("无评分");
return;
} else {
resolve(matchResult[0]);
}
},
onerror(error) {
reject(error);
},
});
});
}
// export function getDbScore(url: string): Promise<string> {
// return new Promise((resolve, reject) => {
// GM_xmlhttpRequest({
// method: "GET",
// url,
// onload: (response) => {
// const doc = new DOMParser().parseFromString(response.responseText, "text/html");
// const plist = doc.querySelector<HTMLElement>(`.panel.movie-panel-info`);
// const innerHtml = plist?.innerHTML;
// const matchResult = innerHtml?.match(/\d\.\d分/);
// if (!innerHtml || !matchResult) {
// reject("无评分");
// return;
// } else {
// resolve(matchResult[0]);
// }
// },
// onerror(error) {
// reject(error);
// },
// });
// });
// }
interface dbResult {
score: string;
release: string;
}
export function parserJavdb(code?: string): Promise<dbResult> {
return new Promise((resolve, reject) => {
if (!code) reject("没找到");
GM_xmlhttpRequest({
url: `https://javdb005.com/search?q=${code}`,
method: "GET",
onload: (response) => {
const doc = new DOMParser().parseFromString(response.responseText, "text/html");
const firstItem = doc.querySelectorAll<HTMLElement>(`.movie-list>.item`)[0];
const titleString = firstItem.querySelector<HTMLElement>(`.video-title>strong`)?.innerHTML;
const releaseString = firstItem.querySelector<HTMLElement>(`.meta`)?.innerHTML.trim();
if (titleString !== code || !releaseString) {
reject("没找到");
} else {
const fullScoreText = firstItem.querySelector<HTMLElement>(`.score .value`)?.innerHTML;
const matchResult = fullScoreText?.match(/\d\.\d*分/);
if (!matchResult) reject("没找到");
else
resolve({
// score: matchResult[0],
score: matchResult[0].replace("分", ""),
release: releaseString,
});
}
},
onerror(error) {
reject(error);
},
});
});
}
export default xhr;
// interface dbResult {
// score: string;
// release: string;
// }
/** 没用了,白写 */
// export function parserJavdb(code?: string): Promise<dbResult> {
// return new Promise((resolve, reject) => {
// if (!code) reject("没找到");
// GM_xmlhttpRequest({
// url: `https://javdb005.com/search?q=${code}`,
// method: "GET",
// onload: (response) => {
// const doc = new DOMParser().parseFromString(response.responseText, "text/html");
// const firstItem = doc.querySelectorAll<HTMLElement>(`.movie-list>.item`)[0];
// const titleString = firstItem.querySelector<HTMLElement>(`.video-title>strong`)?.innerHTML;
// const releaseString = firstItem.querySelector<HTMLElement>(`.meta`)?.innerHTML.trim();
// if (titleString !== code || !releaseString) {
// reject("没找到");
// } else {
// const fullScoreText = firstItem.querySelector<HTMLElement>(`.score .value`)?.innerHTML;
// const matchResult = fullScoreText?.match(/\d\.\d*分/);
// if (!matchResult) reject("没找到");
// else
// resolve({
// // score: matchResult[0],
// score: matchResult[0].replace("分", ""),
// release: releaseString,
// });
// }
// },
// onerror(error) {
// reject(error);
// },
// });
// });
// }

View File

@@ -18,5 +18,6 @@
"paths": { "@/*": ["./src/*"] }
},
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
"exclude": ["**/backup"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -7,7 +7,7 @@ const connectList = siteList.map((site) => site.hostname).concat(["javdb005.com"
const UserscriptConfig: MonkeyUserScript = {
author: "mission522",
version: "1.1.0",
version: "1.1.1",
license: "MIT",
name: "JAV 添加跳转在线观看 三合一",
match: ["*://*/cn/?v=jav*"],