// ==UserScript==
// @name Kemono 使用增強
// @name:zh-TW Kemono 使用增強
// @name:zh-CN Kemono 使用增强
// @name:ja Kemono 使用を強化
// @name:en Kemono Usage Enhancement
// @version 0.0.23
// @author HentiSaru
// @description 側邊欄收縮美化界面 , 自動加載大圖 , 簡易隱藏廣告 , 翻頁優化 , 自動開新分頁
// @description:zh-TW 側邊欄收縮美化界面 , 自動加載大圖 , 簡易隱藏廣告 , 翻頁優化 , 自動開新分頁
// @description:zh-CN 侧边栏收缩美化界面 , 自动加载大图 , 简易隐藏广告 , 翻页优化 , 自动开新分页
// @description:ja サイドバーの収縮によるインターフェースの美化、大画像の自動読み込み、広告の簡易非表示、ページめくりの最適化、新しいページの自動開封
// @description:en Sidebar contraction beautifies interface, automatically loads large images, easily hides ads, optimizes paging, and automatically opens new tabs.
// @match *://kemono.su/*
// @match *://*.kemono.su/*
// @match *://kemono.party/*
// @match *://*.kemono.party/*
// @icon https://cdn-icons-png.flaticon.com/512/2566/2566449.png
// @license MIT
// @namespace https://greasyfork.org/users/989635
// @run-at document-start
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM_addElement
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @require https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js
// @resource font-awesome https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css
// @require https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js
// ==/UserScript==
var xhr = new XMLHttpRequest(),
Url = window.location.href,
parser = new DOMParser(),
buffer = document.createDocumentFragment();
(function () {
let interval, tryerror = 0, dellay = 300;
const pattern = /^(https?:\/\/)?(www\.)?kemono\..+\/.+\/user\/.+\/post\/.+$/,
UserPage = /^(https?:\/\/)?(www\.)?kemono\..+\/.+\/user\/[^\/]+(\?.*)?$/,
PostsPage = /^(https?:\/\/)?(www\.)?kemono\..+\/posts\/?(\?.*)?$/,
DmsPage = /^(https?:\/\/)?(www\.)?kemono\..+\/dms\/?(\?.*)?$/;
async function Main() {
const [list, box, comments, announce] = [ // comments(評論區標題), announce(公告條)
"div.global-sidebar", "div.content-wrapper.shifted", "h2.site-section__subheading", "body > div.content-wrapper.shifted > a"
].map(selector => document.querySelector(selector));
if ((box && list && comments) || (box && list)) {
Beautify(box, list, announce); // 側邊欄收縮
if (pattern.test(Url)) { Additional(comments) }// (帖子內) Ajex 快捷換頁
clearInterval(interval);
} else {
tryerror++;
if (tryerror > 10) { clearInterval(interval) }
}
}
interval = setInterval(() => { Main() }, dellay);
setTimeout(() => {
AdHiding(); // 隱藏廣告
if (pattern.test(Url)) {
OriginalImage(); // 自動大圖
LinkOriented(); // 連結轉換
VideoBeautify(); // 影片美化
}
if (UserPage.test(Url) || PostsPage.test(Url) || DmsPage.test(Url)) {
AjexPostToggle(); // Ajex 換頁
NewTabOpens(); // 自動新分頁
}
}, dellay);
})();
/* ==================== */
/* 美化介面 */
async function Beautify(box, list, announce) {
GM_addStyle(`
.list_column {
opacity: 0;
width: 10rem !important;
transform: translateX(-9rem);
transition: 0.8s;
}
.list_column:hover {
opacity: 1;
transform: translateX(0rem);
}
.main_box {
transition: 0.7s;
}
`);
try {
announce.remove();
box.classList.add("main_box");
box.style.marginLeft = "0rem";
list.classList.add("list_column");
list.addEventListener('mouseenter', function () {
box.style.marginLeft = "10rem";
});
list.addEventListener('mouseleave', function () {
box.style.marginLeft = "0rem";
});
} catch { }
}
async function VideoBeautify() {
let stream, parents;
parents = document.querySelectorAll('ul[style*="text-align: center;list-style-type: none;"] li');
if (parents.length > 0) {
function ReactBeautify({ stream }) {
return React.createElement("video", {
key: "video",
controls: true,
preload: "auto",
style: { width: "80%", height: "80%" },
}, React.createElement("source", {
key: "source",
src: stream.src,
type: stream.type
}));
}
parents.forEach(li => {
stream = li.querySelector("source");
if (stream) {
ReactDOM.render(React.createElement(ReactBeautify, { stream: stream }), li);
} else {
console.log("Debug: Could not find source, please refresh");
}
})
}
}
/* 載入原圖 */
async function OriginalImage() {
GM_addStyle(`
.img-style {
max-width: 100%;
display: block;
margin: 0 auto;
}
`);
let thumbnail, href, img;
thumbnail = document.querySelectorAll("div.post__thumbnail");
if (thumbnail.length > 0) {
function ImgRendering({ ID, href }) {
return React.createElement("a", {
id: ID,
className: "image-link"
}, React.createElement("img", {
key: "img",
src: href.href,
className: "img-style",
onError: function () {
Reload(ID, 15);
}
})
)
}
thumbnail.forEach(async (object, index) => {
object.classList.remove("post__thumbnail");
href = object.querySelector("a");
await ReactDOM.render(React.createElement(ImgRendering, { ID: `IMG-${index}`, href: href }), object);
await new Promise(resolve => setTimeout(resolve, 800));
})
document.querySelectorAll("a.image-link").forEach(link => {
const handleClick = () => {
img = link.querySelector("img");
if (!img.complete) {
img.src = img.src;
} else {
link.removeEventListener("click", handleClick);
}
}
link.addEventListener("click", handleClick);
});
}
}
async function Reload(ID, retry) {
if (retry > 0) {
setTimeout(() => {
let object = document.getElementById(ID), old = object.querySelector("img"), img = document.createElement("img");
img.src = old.src;
img.alt = "Click Reload";
img.className = "img-style";
img.onerror = function () { Reload(ID, retry) };
old.remove();
object.appendChild(buffer.appendChild(img));
retry - 1;
}, 1800);
}
}
/* ==================== */
/* 監聽器的添加與刪除 */
var ListenerRecord = new Map(), listen;
async function addlistener(element, type, listener) {
if (!ListenerRecord.has(element) || !ListenerRecord.get(element).has(type)) {
element.addEventListener(type, listener);
if (!ListenerRecord.has(element)) {
ListenerRecord.set(element, new Map());
}
ListenerRecord.get(element).set(type, listener);
}
}
async function removlistener(element, type) {
if (ListenerRecord.has(element) && ListenerRecord.get(element).has(type)) {
listen = ListenerRecord.get(element).get(type);
element.removeEventListener(type, listen);
ListenerRecord.get(element).delete(type);
}
}
/* ==================== */
/* 簡易隱藏廣告 */
async function AdHiding() {
document.querySelectorAll(".ad-container").forEach(function (element) {
try { element.style.display = "none" } catch { element.style.visibility = "hidden" }
})
let attempts = 0, interval = setInterval(function () {
if (attempts < 5) {
document.querySelectorAll(".root--ujvuu").forEach((element) => {
try {
element.style.opacity = 0;
element.style.visibility = "hidden";
} catch {
element.style.visibility = "hidden";
}
});
attempts++;
} else { clearInterval(interval) }
}, 700);
}
/* 轉換下載連結參數 */
async function LinkOriented() {
document.querySelectorAll("a.post__attachment-link").forEach(link => {
link.setAttribute("download", "");
})
}
/* 底部按鈕創建, 監聽快捷Ajex換頁 */
async function Additional(comments) {
GM_addStyle(GM_getResourceText("font-awesome"));
const prev = document.querySelector("a.post__nav-link.prev");
const next = document.querySelector("a.post__nav-link.next");
const span = document.createElement("span");
const svg = document.createElement("svg");
span.style = "float: right";
span.appendChild(next.cloneNode(true));
svg.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" style="margin-left: 10px;cursor: pointer;">
<style>svg{fill:#e8a17d}</style>
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM135.1 217.4l107.1-99.9c3.8-3.5 8.7-5.5 13.8-5.5s10.1 2 13.8 5.5l107.1 99.9c4.5 4.2 7.1 10.1 7.1 16.3c0 12.3-10 22.3-22.3 22.3H304v96c0 17.7-14.3 32-32 32H240c-17.7 0-32-14.3-32-32V256H150.3C138 256 128 246 128 233.7c0-6.2 2.6-12.1 7.1-16.3z"></path>
</svg>
`
buffer.appendChild(svg);
buffer.appendChild(span);
comments.appendChild(buffer);
addlistener(svg, "click", () => {
document.querySelector("header").scrollIntoView();
})
// 監聽按鍵切換
const main = document.querySelector("main");
addlistener(document, "keydown", event => {
try {
if (event.key === "4") {
event.preventDefault();
removlistener(document, "keydown");
AjexReplace(prev.href, main);
} else if (event.key === "6") {
event.preventDefault();
removlistener(document, "keydown");
AjexReplace(next.href, main);
}
} catch { }
})
}
GM_addStyle(`
.gif-overlay {
position: absolute;
opacity: 0.4;
top: 50%;
left: 50%;
width: 70%;
height: 70%;
z-index: 9999;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.diluted-information {
opacity: 0.4;
}
`);
/* 將瀏覽帖子頁面都變成開新分頁, 帖子說明文字淡化, 和滑鼠懸浮恢復 */
async function NewTabOpens() {
const card = document.querySelectorAll("div.card-list__items article a");
card.forEach(link => {
link.querySelector("header").classList.add("diluted-information");
link.querySelector("footer").classList.add("diluted-information");
addlistener(link, "click", event => {
event.preventDefault();
GM_openInTab(link.href, { active: false, insert: true });
})
addlistener(link, "mouseenter", () => {
link.querySelector("header").classList.remove("diluted-information");
link.querySelector("footer").classList.remove("diluted-information");
})
addlistener(link, "mouseleave", () => {
link.querySelector("header").classList.add("diluted-information");
link.querySelector("footer").classList.add("diluted-information");
})
});
}
/* ==================== */
/* Ajex 替換頁面的初始化 */
async function Initialization() {
let interval = setInterval(function () {
const comments = document.querySelector("h2.site-section__subheading");
if (comments) {
Additional(comments);
clearInterval(interval);
}
}, 300);
setTimeout(OriginalImage, 500);
setTimeout(VideoBeautify, 500);
document.querySelector("h1.post__title").scrollIntoView(); // 滾動到上方
}
/* React 渲染優化 */
function ReactRendering({ content }) {
return React.createElement("div", { dangerouslySetInnerHTML: { __html: content } });
}
async function AjexReplace(url, old_main) {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let New_data = parser.parseFromString(xhr.responseText, 'text/html');
let New_main = New_data.querySelector("main");
ReactDOM.render(React.createElement(ReactRendering, { content: New_main.innerHTML }), old_main);
history.pushState(null, null, url);
setTimeout(Initialization(), 500);
}
};
xhr.open("GET", url, true);
xhr.send();
}
/* 帖子切換 */
async function AjexPostToggle() {
let Old_data, New_data, item;
async function Request(link) {
item = document.querySelector("div.card-list__items");
item.style.position = "relative";
GM_addElement(item, "img", {
src: "https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.3/images/loading.gif",
class: "gif-overlay"
});
GM_xmlhttpRequest({
method: "GET",
url: link,
nocache: false,
onload: response => {
Old_data = document.querySelector("section");
New_data = parser.parseFromString(response.responseText, "text/html").querySelector("section");
ReactDOM.render(React.createElement(ReactRendering, { content: New_data.innerHTML }), Old_data);
history.pushState(null, null, link);
AjexPostToggle();
NewTabOpens();
AdHiding();
}
});
}
try {
const menu = document.querySelectorAll("menu a");
menu.forEach(ma => {
addlistener(ma, "click", (event) => {
event.preventDefault();
Request(ma.href);
})
});
} catch {}
}