// ==UserScript==
// @name PagePilot
// @name:zh-CN 键页通
// @namespace https://gf.qytechs.cn/scripts/541642
// @version 0.1.3
// @author oajsdfk
// @description PagePilot allows custom keybindings and CSS selectors for page-turning buttons to be uniquely configured per URL. Automatically detects webpage addresses and applies your preset rules, enabling precise one-click navigation across all sites.
// @description:zh-CN 键页通支持为不同网页(URL)独立配置按键策略与翻页按钮定位规则。自动识别当前访问网址,精准匹配预设的自定义按键及翻页按钮CSS选择器,实现一键翻页的个性化操控体验。
// @license MIT
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function () {
'use strict';
function pilot(url, event, options2) {
const find = (keys) => {
if (!keys) return false;
if (keys.key?.find((i) => i === event.key)) return true;
if (keys.code?.find((i) => i === event.code)) return true;
return false;
};
const g = [];
for (const [k, v] of Object.entries(options2.globalKeys)) {
if (find(v)) g.push(k);
}
function matchGlobal(k, p) {
if (find(p.keys)) return true;
if (typeof p.globalKeys === "boolean") {
if (!p.globalKeys) return false;
return g.indexOf(k) != -1;
} else if (typeof p.globalKeys === "string") {
return g.indexOf(p.globalKeys) != -1;
}
return g.indexOf(k) != -1;
}
function matchKey(p) {
if (p.cur) {
if (!p.next && !p.prev && !p.other) {
for (const i of g) {
if (i === "next") return { child: p.cur, sibling: 1 };
if (i === "prev") return { child: p.cur, sibling: -1 };
}
return;
}
for (const k of ["prev", "next"]) {
if (!p[k]) continue;
let r = typeof p[k] === "string" ? {
child: p.cur,
sibling: k === "next" ? 1 : -1,
deep: { child: p[k] }
} : p[k];
if (matchGlobal(k, r)) return r;
}
for (const [k, v] of Object.entries(p.other ?? [])) {
let r = typeof v === "string" ? {
child: p.cur,
sibling: k === "next" ? 1 : -1,
deep: { child: v }
} : v;
if (matchGlobal(k, r)) return r;
}
} else {
for (const k of ["prev", "next"]) {
if (!p[k]) continue;
let r = typeof p[k] === "string" ? { child: p[k] } : p[k];
if (matchGlobal(k, r)) return r;
}
for (const [k, v] of Object.entries(p.other ?? [])) {
let r = typeof v === "string" ? { child: v } : v;
if (matchGlobal(k, r)) return r;
}
}
return;
}
for (const [r, p] of Object.entries(options2.pilots)) {
if (!url.match(new RegExp(r))) continue;
const q = matchKey(p);
if (!q) continue;
const { child, sibling, deep } = q;
if (!child) continue;
const cur = document.querySelector(child);
const ele = queryDeep({ sibling, deep }, cur);
if (!ele) continue;
ele.click();
return ele;
}
return null;
}
function queryDeep({ sibling, child, deep }, e) {
if (child) e = e?.querySelector(child);
if (sibling) {
let c = Math.abs(sibling);
while (c > 0 && e) {
c--;
e = sibling > 0 ? e.nextElementSibling : e.previousElementSibling;
}
if (c !== 0) return null;
}
return deep ? queryDeep(deep, e) : e;
}
const lastOptionsVer = GM_getValue("pagepilot_options_ver");
let options = GM_getValue("pagepilot_options");
const optionsVer = 1;
if (!options || lastOptionsVer !== optionsVer) {
options = {
globalKeys: {
// 绑定名称的通用按键,所有网站的元素根据名称自动绑定到这些按键上
// Common keys bound by name, elements on all sites will be automatically bound to these keys
prev: { code: ["ArrowUp", "PageUp", "KeyP", "KeyD"] },
next: { code: ["ArrowDown", "PageDown", "KeyN", "KeyF"] },
top: { code: ["Home", "KeyT"] },
bottom: { code: ["End", "KeyB"] }
},
pilots: {
// 站点正则 => 元素选择器或按键
// site regex => element selector or key
"http://example1.com(/.*)?": {
// 当点击元素对应的按键时候,触发元素的点击事件: PageUp => querySelector('.pg .prev_btn').click()
// When the corresponding key is pressed, trigger the element's click event: PageUp => querySelector('.pg .prev_btn').click()
prev: ".pg .prev_btn",
next: ".pg .next_btn"
},
// 通过当前页元素的兄弟来定位翻页按钮
// locate the page-turning buttons by the siblings of the current page element
"https://(.*.)?example2.org(/.*)?": { cur: "#cur_page" },
// 通过当前页元素的兄弟的子孙来定位翻页按钮
// locate the page-turning buttons by the descendants of the siblings of the current page element
"https?://(.*.)?example3.org(/.*)?": {
cur: "#cur_page",
prev: "a",
// querySelector('#cur_page').prevElment.querySelector(a).click()
next: "a"
// querySelector('#cur_page').nextElment.querySelector(a).click()
},
"https?://(.*.)?example4.org(/.*)?": {
// 多层级子孙、兄弟的翻页按钮的定位规则: querySelector('.nav .pg').prevE.prevE.prevE.querySelector(a).click()
// multi-level descendants and siblings page button location rules (): querySelector('.nav .pg').prevE.prevE.prevE.querySelector(a).click()
prev: {
child: ".nav",
deep: {
child: ".pg",
sibling: -3,
deep: {
child: "a"
}
}
},
next: {
child: ".nav",
deep: {
child: ".pg",
sibling: 5,
// querySelector('.nav .pg') .nextE x5 .querySelector(a).click()
deep: {
child: "a"
}
}
},
// 定义翻页以外的任意按钮
// define any buttons other than page turning
other: {
// 显示指定绑定或者禁用全局按键
// Explicitly bind or disable global keys
top: "#top",
bottom: {
// 显示指定绑定或者禁用全局按键
// Explicitly bind or disable global keys
globalKeys: false,
// disable
child: "#bottomBtn",
// 指定只对该网站生效的按键
// Specify keys that only take effect on this site
keys: { code: ["KeyN"] }
},
close: {
child: "#closeBtn",
keys: { code: ["KeyC"] }
}
}
}
}
};
GM_setValue("pagepilot_options", options);
GM_setValue("pagepilot_options_ver", optionsVer);
}
console.log({ pilot_options: options });
window.addEventListener("keyup", function(e) {
const ae = document.activeElement;
if (ae && ["input", "select", "button", "textarea"].indexOf(
ae.tagName.toLowerCase()
) !== -1) return;
console.log({ key: e.key, code: e.code, options });
pilot(window.location.href, e, options);
});
})();