Sleazy Fork is available in English.

喵绅士(nyahentai)

正式可用,让新版喵绅士有长条预览功能

  1. // ==UserScript==
  2. // @name 喵绅士(nyahentai)
  3. // @namespace https://github.com/dffxd-suntra/nyahentai-plus
  4. // @version 3.0
  5. // @description 正式可用,让新版喵绅士有长条预览功能
  6. // @homepageURL https://github.com/dffxd-suntra/nyahentai-plus
  7. // @supportURL https://github.com/dffxd-suntra/nyahentai-plus
  8. // @match *://nyahentai.red/*
  9. // @match *://nhentai.xxx/*
  10. // @match *://nhentai.net/*
  11. // @icon https://nyahentai.red/front/favicon.ico
  12. // @require https://cdn.jsdelivr.net/npm/jquery@3.7.0/dist/jquery.min.js
  13. // @require https://cdn.jsdelivr.net/npm/lazysizes@5.3.2/lazysizes.min.js
  14. // @require https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js
  15. // @require https://cdn.jsdelivr.net/npm/keyboardjs@2.7.0/dist/keyboard.min.js
  16. // @author Suntra
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @license MIT
  20. // ==/UserScript==
  21.  
  22. (function () {
  23. let loadingImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE7WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMy0wNS0zMFQyMjozMzoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTlmYWFhY2MtZGQ5Zi0yMDRlLTk5MGQtMWZiNzFiYjhhYThhIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE5ZmFhYWNjLWRkOWYtMjA0ZS05OTBkLTFmYjcxYmI4YWE4YSIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjE5ZmFhYWNjLWRkOWYtMjA0ZS05OTBkLTFmYjcxYmI4YWE4YSIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMSI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTlmYWFhY2MtZGQ5Zi0yMDRlLTk5MGQtMWZiNzFiYjhhYThhIiBzdEV2dDp3aGVuPSIyMDIzLTA1LTMwVDIyOjMzOjE0KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjEuMSAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ejvILQAAAApJREFUCJljKAcAAHkAeO/tISkAAAAASUVORK5CYII=";
  24. let imgWidth = GM_getValue("imgWidth", 60);
  25. let scrolling = false;
  26. let preUrl = null;
  27. // 讲htmlStr加载为document
  28. async function loadHtml(url) {
  29. const response = await axios(url);
  30. if (response.data == "" || response.status != 200) {
  31. console.error(response);
  32. throw new Error("网页获取错误");
  33. }
  34. return new window.DOMParser().parseFromString(response.data, "text/html");
  35. }
  36. // 根据图片阅览页面获取链接,这是备用方法,图片加载错误触发
  37. async function getPicByPage(url) {
  38. let detailDocument = await loadHtml(url);
  39. return $("#image-container > a > img", detailDocument).attr("src");
  40. }
  41. // 开始阅览
  42. async function startView(url) {
  43. if (preUrl != url) {
  44. $("#nyap-read-page-img").html("");
  45. }
  46. if (!url.endsWith("/")) {
  47. url += "/";
  48. }
  49. preUrl = url;
  50. let detailDocument = await loadHtml(url);
  51. let pages = parseInt($("#tags", detailDocument).children(":contains('Pages')").find(".tag").text());
  52. let tempUrl = $("#cover > a > img", detailDocument).attr("src").split("/").slice(0, -1).join("/") + "/";
  53. // 根据封面的图片后缀预测正文后缀
  54. let picSuffix = $("#cover > a > img", detailDocument).attr("src").split(".").pop();
  55. console.log(`pages: ${pages}\ntempUrl: ${tempUrl}\npicSuffix: ${picSuffix}`);
  56.  
  57. // 一下都添加,但是有lazysize, 懒加载让页面不卡
  58. for (let i = 1; i <= pages; i++) {
  59. $("#nyap-read-page-img").append(
  60. $("<span>")
  61. .text(`${i}/${pages} page`)
  62. .css({
  63. color: "gray",
  64. position: "absolute",
  65. left: 0
  66. }),
  67. $("<img>")
  68. .attr("src", loadingImg)
  69. .attr("data-src", `${tempUrl + i}.${picSuffix}`)
  70. .addClass("lazyload")
  71. .css({
  72. width: "100%",
  73. padding: 0,
  74. margin: 0,
  75. display: "block"
  76. })
  77. // 出错是图片后缀出问题了,将图片浏览页面打开获取链接
  78. .on("error", async function (event) {
  79. $(this).attr("src", loadingImg);
  80. $(this).attr("src", await getPicByPage(`${url}${i}/`));
  81. })
  82. );
  83. }
  84. }
  85. // 记忆化改变宽度
  86. function changeWidth(x) {
  87. imgWidth = Math.max(1, imgWidth + x);
  88. GM_setValue("imgWidth", imgWidth);
  89. // 以屏幕中线缩放,避免改变阅览进度
  90. let readProgress = ($("#nyap-read-page").scrollTop() + $(window).height() / 2) / $("#nyap-read-page").prop("scrollHeight");
  91. $("#nyap-read-page-img").css("width", imgWidth + "%");
  92. $("#showWidth").text(imgWidth + "%");
  93. $("#nyap-read-page").scrollTop(readProgress * $("#nyap-read-page").prop("scrollHeight") - $(window).height() / 2);
  94. }
  95. // 滚动函数,用 requestAnimationFrame, 不会卡顿
  96. // 因为 requestAnimationFrame 一般是一帧运行一次
  97. function startScroll(ms) {
  98. scrolling = true;
  99. let previousTimeStamp;
  100. let sum = 0;
  101. function step(time) {
  102. if (previousTimeStamp != undefined) {
  103. sum += ($(window).height() / ms) * (time - previousTimeStamp);
  104. $("#nyap-read-page").scrollTop($("#nyap-read-page").scrollTop() + sum);
  105. // 页面的滚动是以像素为单位(int),所以小于1像素要等下一次一起滚动
  106. sum %= 1;
  107. }
  108. if (scrolling) {
  109. previousTimeStamp = time;
  110. // 进行下一次的滚动
  111. window.requestAnimationFrame(step);
  112. }
  113. }
  114. window.requestAnimationFrame(step);
  115. $("#nyap-read-page-scroll").text("结束");
  116. }
  117. function endScroll() {
  118. scrolling = false;
  119. $("#nyap-read-page-scroll").text("滚动");
  120. }
  121. // 页面样式
  122. $("body").prepend(`
  123. <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">
  124. <div style="position: fixed;bottom: 10px;right: 10px;">
  125. <h1 id="showWidth" style="text-shadow: 0px 0px 4px black;">${imgWidth}%</h1>
  126. <div id="nyap-read-page-img-change-width">
  127. <button type="button" class="btn btn-primary">+1</button><br>
  128. <button type="button" class="btn btn-primary">+5</button><br>
  129. <button type="button" class="btn btn-primary">+10</button><br>
  130. <button type="button" class="btn btn-primary">-10</button><br>
  131. <button type="button" class="btn btn-primary">-5</button><br>
  132. <button type="button" class="btn btn-primary">-1</button><br>
  133. </div>
  134. <input type="text" id="nyap-read-page-scroll-speed" class="btn btn-secondary" title="几毫秒滚完一个屏幕" style="width: 5em;" /><br>
  135. <button type="button" id="nyap-read-page-scroll" class="btn btn-primary">滚动</button><br>
  136. <button type="button" id="nyap-read-page-hide" class="btn btn-primary">关闭</button><br>
  137. <button type="button" id="nyap-read-page-toTop" class="btn btn-primary">顶部</button>
  138. </div>
  139. <center>
  140. <div id="nyap-read-page-img" style="width: ${imgWidth}%"></div>
  141. </center>
  142. </div>
  143. `);
  144. // 检测详情界面
  145. if (/^\/g\/.+\/?$/.test(location.pathname)) {
  146. $("#info > div").prepend($(`<button class="btn btn-primary" id="nyap-read-page-show">垂直阅读</button>`).data("page-link", location.href));
  147. }
  148. // 添加快捷阅读标志
  149. $(".gallery > .cover").each(function (index, node) {
  150. $(node).append(
  151. $(`<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"))
  152. );
  153. });
  154. // 记忆化
  155. $("#nyap-read-page-scroll-speed").val(GM_getValue("scrollSpeed", 8000));
  156. // 切换开,关
  157. $("*#nyap-read-page-show").click(function (event) {
  158. event.preventDefault();
  159. keyboardJS.setContext("view");
  160. $("#nyap-read-page").scrollTop(0);
  161. $("body").css("overflow", "hidden");
  162. $("#nyap-read-page").show();
  163. startView($(this).data("page-link"));
  164. });
  165. $("#nyap-read-page-hide").click(function () {
  166. keyboardJS.setContext("index");
  167. $("body").css("overflow", "");
  168. $("#nyap-read-page").hide();
  169. endScroll();
  170. });
  171. // totop
  172. $("#nyap-read-page-toTop").click(function () {
  173. $("#nyap-read-page").scrollTop(0);
  174. });
  175. // 切换宽度
  176. $("#nyap-read-page-img-change-width").click(function ({ target }) {
  177. if ($(target).attr("id") == "nyap-read-page-img-change-width") {
  178. return;
  179. }
  180. changeWidth(parseInt($(target).text()));
  181. });
  182. // 设置滚动
  183. $("#nyap-read-page-scroll").click(function () {
  184. if (scrolling) {
  185. endScroll();
  186. } else {
  187. let ms = parseInt($("#nyap-read-page-scroll-speed").val());
  188. if (!ms) {
  189. return;
  190. }
  191. startScroll(ms);
  192. }
  193. });
  194. $("#nyap-read-page-scroll-speed").on("input", function () {
  195. GM_setValue("scrollSpeed", $(this).val());
  196. });
  197. // 快捷键
  198. keyboardJS.withContext("index", function () {
  199. keyboardJS.bind("left", function (e) {
  200. let url = new URL(location.href);
  201. let page = Math.max(parseInt(url.searchParams.get("page")), 1) || 1;
  202. if (page == 1) {
  203. return;
  204. }
  205. page--;
  206. url.searchParams.set("page", page);
  207. location.href = url.href;
  208. });
  209. keyboardJS.bind("right", function (e) {
  210. let url = new URL(location.href);
  211. let page = Math.max(parseInt(url.searchParams.get("page")), 1) || 1;
  212. page++;
  213. url.searchParams.set("page", page);
  214. location.href = url.href;
  215. });
  216. });
  217. keyboardJS.withContext("view", function () {
  218. keyboardJS.bind("esc", function (e) {
  219. if (scrolling) {
  220. endScroll();
  221. return;
  222. }
  223. $("#nyap-read-page-hide").click();
  224. });
  225. });
  226. keyboardJS.setContext("index");
  227. })();