// ==UserScript==
// @name 喵绅士(nyahentai)
// @namespace https://github.com/dffxd-suntra/nyahentai-plus
// @version 3.0
// @description 正式可用,让新版喵绅士有长条预览功能
// @homepageURL https://github.com/dffxd-suntra/nyahentai-plus
// @supportURL https://github.com/dffxd-suntra/nyahentai-plus
// @match *://nyahentai.red/*
// @match *://nhentai.xxx/*
// @match *://nhentai.net/*
// @icon https://nyahentai.red/front/favicon.ico
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.0/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/lazysizes@5.3.2/lazysizes.min.js
// @require https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js
// @require https://cdn.jsdelivr.net/npm/keyboardjs@2.7.0/dist/keyboard.min.js
// @author Suntra
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// ==/UserScript==
(function () {
let loadingImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE7WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMy0wNS0zMFQyMjozMzoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTlmYWFhY2MtZGQ5Zi0yMDRlLTk5MGQtMWZiNzFiYjhhYThhIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE5ZmFhYWNjLWRkOWYtMjA0ZS05OTBkLTFmYjcxYmI4YWE4YSIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjE5ZmFhYWNjLWRkOWYtMjA0ZS05OTBkLTFmYjcxYmI4YWE4YSIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMSI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTlmYWFhY2MtZGQ5Zi0yMDRlLTk5MGQtMWZiNzFiYjhhYThhIiBzdEV2dDp3aGVuPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ejvILQAAAApJREFUCJljKAcAAHkAeO/tISkAAAAASUVORK5CYII=";
let imgWidth = GM_getValue("imgWidth", 60);
let scrolling = false;
let preUrl = null;
// 讲htmlStr加载为document
async function loadHtml(url) {
const response = await axios(url);
if (response.data == "" || response.status != 200) {
console.error(response);
throw new Error("网页获取错误");
}
return new window.DOMParser().parseFromString(response.data, "text/html");
}
// 根据图片阅览页面获取链接,这是备用方法,图片加载错误触发
async function getPicByPage(url) {
let detailDocument = await loadHtml(url);
return $("#image-container > a > img", detailDocument).attr("src");
}
// 开始阅览
async function startView(url) {
if (preUrl != url) {
$("#nyap-read-page-img").html("");
}
if (!url.endsWith("/")) {
url += "/";
}
preUrl = url;
let detailDocument = await loadHtml(url);
let pages = parseInt($("#tags", detailDocument).children(":contains('Pages')").find(".tag").text());
let tempUrl = $("#cover > a > img", detailDocument).attr("src").split("/").slice(0, -1).join("/") + "/";
// 根据封面的图片后缀预测正文后缀
let picSuffix = $("#cover > a > img", detailDocument).attr("src").split(".").pop();
console.log(`pages: ${pages}\ntempUrl: ${tempUrl}\npicSuffix: ${picSuffix}`);
// 一下都添加,但是有lazysize, 懒加载让页面不卡
for (let i = 1; i <= pages; i++) {
$("#nyap-read-page-img").append(
$("<span>")
.text(`${i}/${pages} page`)
.css({
color: "gray",
position: "absolute",
left: 0
}),
$("<img>")
.attr("src", loadingImg)
.attr("data-src", `${tempUrl + i}.${picSuffix}`)
.addClass("lazyload")
.css({
width: "100%",
padding: 0,
margin: 0,
display: "block"
})
// 出错是图片后缀出问题了,将图片浏览页面打开获取链接
.on("error", async function (event) {
$(this).attr("src", loadingImg);
$(this).attr("src", await getPicByPage(`${url}${i}/`));
})
);
}
}
// 记忆化改变宽度
function changeWidth(x) {
imgWidth = Math.max(1, imgWidth + x);
GM_setValue("imgWidth", imgWidth);
// 以屏幕中线缩放,避免改变阅览进度
let readProgress = ($("#nyap-read-page").scrollTop() + $(window).height() / 2) / $("#nyap-read-page").prop("scrollHeight");
$("#nyap-read-page-img").css("width", imgWidth + "%");
$("#showWidth").text(imgWidth + "%");
$("#nyap-read-page").scrollTop(readProgress * $("#nyap-read-page").prop("scrollHeight") - $(window).height() / 2);
}
// 滚动函数,用 requestAnimationFrame, 不会卡顿
// 因为 requestAnimationFrame 一般是一帧运行一次
function startScroll(ms) {
scrolling = true;
let previousTimeStamp;
let sum = 0;
function step(time) {
if (previousTimeStamp != undefined) {
sum += ($(window).height() / ms) * (time - previousTimeStamp);
$("#nyap-read-page").scrollTop($("#nyap-read-page").scrollTop() + sum);
// 页面的滚动是以像素为单位(int),所以小于1像素要等下一次一起滚动
sum %= 1;
}
if (scrolling) {
previousTimeStamp = time;
// 进行下一次的滚动
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
$("#nyap-read-page-scroll").text("结束");
}
function endScroll() {
scrolling = false;
$("#nyap-read-page-scroll").text("滚动");
}
// 页面样式
$("body").prepend(`
<div style="position: fixed;top: 0;left: 0;right: 0;bottom: 0;z-index: 114514;display: none;background: rgba(0, 0, 0, 95%);overflow: auto;-webkit-user-select: none;user-select: none;" id="nyap-read-page">
<div style="position: fixed;bottom: 10px;right: 10px;">
<h1 id="showWidth" style="text-shadow: 0px 0px 4px black;">${imgWidth}%</h1>
<div id="nyap-read-page-img-change-width">
<button type="button" class="btn btn-primary">+1</button><br>
<button type="button" class="btn btn-primary">+5</button><br>
<button type="button" class="btn btn-primary">+10</button><br>
<button type="button" class="btn btn-primary">-10</button><br>
<button type="button" class="btn btn-primary">-5</button><br>
<button type="button" class="btn btn-primary">-1</button><br>
</div>
<input type="text" id="nyap-read-page-scroll-speed" class="btn btn-secondary" title="几毫秒滚完一个屏幕" style="width: 5em;" /><br>
<button type="button" id="nyap-read-page-scroll" class="btn btn-primary">滚动</button><br>
<button type="button" id="nyap-read-page-hide" class="btn btn-primary">关闭</button><br>
<button type="button" id="nyap-read-page-toTop" class="btn btn-primary">顶部</button>
</div>
<center>
<div id="nyap-read-page-img" style="width: ${imgWidth}%"></div>
</center>
</div>
`);
// 检测详情界面
if (/^\/g\/.+\/?$/.test(location.pathname)) {
$("#info > div").prepend($(`<button class="btn btn-primary" id="nyap-read-page-show">垂直阅读</button>`).data("page-link", location.href));
}
// 添加快捷阅读标志
$(".gallery > .cover").each(function (index, node) {
$(node).append(
$(`<a id="nyap-read-page-show" style="position: absolute;right: 0;bottom: 0;"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16"><path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/></svg></a>`).data("page-link", $(node).attr("href"))
);
});
// 记忆化
$("#nyap-read-page-scroll-speed").val(GM_getValue("scrollSpeed", 8000));
// 切换开,关
$("*#nyap-read-page-show").click(function (event) {
event.preventDefault();
keyboardJS.setContext("view");
$("#nyap-read-page").scrollTop(0);
$("body").css("overflow", "hidden");
$("#nyap-read-page").show();
startView($(this).data("page-link"));
});
$("#nyap-read-page-hide").click(function () {
keyboardJS.setContext("index");
$("body").css("overflow", "");
$("#nyap-read-page").hide();
endScroll();
});
// totop
$("#nyap-read-page-toTop").click(function () {
$("#nyap-read-page").scrollTop(0);
});
// 切换宽度
$("#nyap-read-page-img-change-width").click(function ({ target }) {
if ($(target).attr("id") == "nyap-read-page-img-change-width") {
return;
}
changeWidth(parseInt($(target).text()));
});
// 设置滚动
$("#nyap-read-page-scroll").click(function () {
if (scrolling) {
endScroll();
} else {
let ms = parseInt($("#nyap-read-page-scroll-speed").val());
if (!ms) {
return;
}
startScroll(ms);
}
});
$("#nyap-read-page-scroll-speed").on("input", function () {
GM_setValue("scrollSpeed", $(this).val());
});
// 快捷键
keyboardJS.withContext("index", function () {
keyboardJS.bind("left", function (e) {
let url = new URL(location.href);
let page = Math.max(parseInt(url.searchParams.get("page")), 1) || 1;
if (page == 1) {
return;
}
page--;
url.searchParams.set("page", page);
location.href = url.href;
});
keyboardJS.bind("right", function (e) {
let url = new URL(location.href);
let page = Math.max(parseInt(url.searchParams.get("page")), 1) || 1;
page++;
url.searchParams.set("page", page);
location.href = url.href;
});
});
keyboardJS.withContext("view", function () {
keyboardJS.bind("esc", function (e) {
if (scrolling) {
endScroll();
return;
}
$("#nyap-read-page-hide").click();
});
});
keyboardJS.setContext("index");
})();