您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button at the bottom right of all kemono-supported creator websites (+ onlyfans) that redirects to the corresponding kemono/coomer page.
当前为
// ==UserScript== // @name Kemono Browser // @namespace Violentmonkey Scripts // @version 1.4.0 // @description Adds a button at the bottom right of all kemono-supported creator websites (+ onlyfans) that redirects to the corresponding kemono/coomer page. // @author zWolfrost // @license MIT // @match https://*.patreon.com/* // @match https://*.fanbox.cc/* // @match https://*.pixiv.net/* // @match https://*.fantia.jp/* // @match https://*.boosty.to/* // @match https://*.dlsite.com/* // @match https://*.gumroad.com/* // @match https://*.subscribestar.com/* // @match https://*.subscribestar.adult/* // @match https://*.onlyfans.com/* // @icon https://kemono.su/static/favicon.ico // @grant GM.xmlHttpRequest // ==/UserScript== "use strict" const UPDATE_POLLING_MS = 222; //time in milliseconds for the script to update the button. i generally don't recommend setting it to less than 100. const PROMPT_BTN = document.createElement("a"); initPromptButton(); let lastURL = ""; setInterval(update, UPDATE_POLLING_MS); function initPromptButton() { const BTN_ANIMATIONS = true; //whether to enable button animations. const OPEN_IN_NEW_TAB = true; //whether to open the url in a new tab by default. const PROMPT_BTN_ID = "kemono-url-btn"; const BTN_BASE_CSS = ` #${PROMPT_BTN_ID} { display: none; position: fixed !important; bottom: 0px; right: 0px; z-index: 10000000 !important; min-width: 0 !important; min-height: 0 !important; max-width: none !important; max-height: none !important; padding: 4px !important; margin: 12px !important; font-family: arial !important; font-weight: bold !important; font-size: 18px !important; text-transform: capitalize; line-height: normal !important; text-decoration: none !important; cursor: pointer !important; box-shadow: black 0.5px 0.5px, black 1px 1px, black 1.5px 1.5px, black 2px 2px, black 2.5px 2.5px, black 3px 3px, black 3.5px 3.5px, black 4px 4px; } #${PROMPT_BTN_ID}:hover { filter: brightness(90%); } ` const BTN_ANIMATIONS_CSS = ` #${PROMPT_BTN_ID}:active { bottom: -4px; right: -4px; box-shadow: none; filter: brightness(80%); animation: press 0.05s ease-in-out; } @keyframes press { from { bottom: 0px; right: 0px; box-shadow: black 0.5px 0.5px, black 1px 1px, black 1.5px 1.5px, black 2px 2px, black 2.5px 2.5px, black 3px 3px, black 3.5px 3.5px, black 4px 4px; } to { bottom: -4px; right: -4px; } } ` appendCSS(BTN_BASE_CSS); if (BTN_ANIMATIONS) appendCSS(BTN_ANIMATIONS_CSS); PROMPT_BTN.id = PROMPT_BTN_ID; PROMPT_BTN.target = OPEN_IN_NEW_TAB ? "_blank" : "_self"; document.body.prepend(PROMPT_BTN) } async function update() { const domain = window.location.hostname.split(".").slice(-2).join(".") const url = domainMethods[domain]?.(); if (url) { if (url != lastURL) { //console.log(url) lastURL = url promptButton(url, buttonPresets["pending"]) const state = await getCreatorState(url) promptButton(url, buttonPresets[state]) } } else { lastURL = null PROMPT_BTN.style.display = "none" } } const buttonPresets = { "found": {stateText: "found", textColor: "white", backgroundColor: "green"}, "incomplete": {stateText: "incomplete", textColor: "black", backgroundColor: "gold"}, "missing": {stateText: "missing", textColor: "white", backgroundColor: "red"}, "pending": {stateText: "pending...", textColor: "white", backgroundColor: "gray"}, "error": {stateText: "unknown (server error)", textColor: "white", backgroundColor: "gray"}, } function promptButton(url, {stateText, textColor, backgroundColor}) { const serviceName = url.split("/")[3]; PROMPT_BTN.href = url; PROMPT_BTN.innerText = `kemono ${serviceName}: ${stateText}`; PROMPT_BTN.style.color = textColor; PROMPT_BTN.style.backgroundColor = backgroundColor; PROMPT_BTN.style.display = "block"; } function appendCSS(css) { const style = document.createElement("style"); style.appendChild(document.createTextNode(css)); document.head.append(style); } async function getCreatorState(url) { if (url) { const response = await new Promise(resolve => { GM.xmlHttpRequest({ url: url, method: "HEAD", onload: resolve }); }); const redirectUrl = response?.finalUrl; if (redirectUrl == url) return "found"; else if (redirectUrl.includes("user")) return "incomplete"; else if (redirectUrl.includes("artists")) return "missing"; } return "error" } const domainMethods = { "patreon.com": getRedirectURLFromPatreon, "fanbox.cc": getRedirectURLFromFanbox, "pixiv.net": getRedirectURLFromPixiv, "fantia.jp": getRedirectURLFromFantia, "boosty.to": getRedirectURLFromBoosty, "dlsite.com": getRedirectURLFromDLsite, "gumroad.com": getRedirectURLFromGumroad, "subscribestar.com": getRedirectURLFromSubscribeStar, "subscribestar.adult": getRedirectURLFromSubscribeStar, "onlyfans.com": getRedirectURLFromOnlyFans, } function getRedirectURLFromPatreon() { function getPatreonUserID() //html { return getStringBetween(document.documentElement.innerHTML, `"creator":{"data":{"id":"`, `"`) } function getPatreonPostID() //url { return getURLPathAfter(`posts`)?.split(`-`).at(-1) ?? null } return getRedirectURL({ domain: "kemono.su", service: "patreon", userID: getPatreonUserID(), postID: getPatreonPostID() }) } function getRedirectURLFromFanbox() { function getFanboxUserID() //html { return getStringBetween(document.documentElement.innerHTML, `creator/`, `/`) } function getFanboxPostID() //url { return getURLPathAfter(`posts`) } return getRedirectURL({ domain: "kemono.su", service: "fanbox", userID: getFanboxUserID(), postID: getFanboxPostID() }) } function getRedirectURLFromPixiv() { function getPixivUserID() //url { return getURLPathAfter(`users`) } function getPixivUserID2nd() //html { return getStringBetween(document.documentElement.innerHTML, `users/`, `"`) } return getRedirectURL({ domain: "kemono.su", service: "fanbox", userID: getPixivUserID() ?? getPixivUserID2nd() }) } function getRedirectURLFromFantia() { function getFantiaUserID() //url { return getURLPathAfter(`fanclubs`) } function getFantiaUserID2nd() //html { return getStringBetween(document.documentElement.innerHTML, `fanclubs/`, `"`) } function getFantiaPostID() //url { return getURLPathAfter(`posts`) } return getRedirectURL({ domain: "kemono.su", service: "fantia", userID: getFantiaUserID() ?? getFantiaUserID2nd(), postID: getFantiaPostID() }) } function getRedirectURLFromBoosty() { function getBoostyUserID() //url { return getURLPathAfter(`boosty.to`) } function getBoostyPostID() //url { return getURLPathAfter(`posts`) } return getRedirectURL({ domain: "kemono.su", service: "boosty", userID: getBoostyUserID(), postID: getBoostyPostID() }) } function getRedirectURLFromDLsite() { function getDLsiteUserID() //html { return getStringBetween(document.documentElement.innerHTML, `maker_id/`, `.`) } function getDLsitePostID() //url { let postID = getURLPathAfter(`product_id`)?.match(new RegExp(`\\d`, "g")).join("") return postID ? "RE" + postID : null } return getRedirectURL({ domain: "kemono.su", service: "dlsite", userID: getDLsiteUserID(), postID: getDLsitePostID() }) } function getRedirectURLFromGumroad() { function getGumroadUserID() //html { return getStringBetween(document.querySelector(`script.js-react-on-rails-component`)?.innerText, `id":"`, `"`) } function getGumroadPostID() //url { return getURLPathAfter(`l`) } return getRedirectURL({ domain: "kemono.su", service: "gumroad", userID: getGumroadUserID(), postID: getGumroadPostID() }) } function getRedirectURLFromSubscribeStar() { function getSubScribeStarUserID() //html { return document.querySelector(`img[data-type="avatar"]`)?.alt.toLowerCase() ?? null } function getSubScribeStarPostID() //url { return getURLPathAfter(`posts`) } return getRedirectURL({ domain: "kemono.su", service: "subscribestar", userID: getSubScribeStarUserID(), postID: getSubScribeStarPostID() }) } function getRedirectURLFromOnlyFans() { function getOnlyFansUserID() //html { return document.querySelector(`#content a.g-avatar`)?.getAttribute("href").slice(1) } function getOnlyFansPostID() //url { return document.querySelector(`#content div.b-post`)?.id.slice(7) } return getRedirectURL({ domain: "coomer.su", service: "onlyfans", userID: getOnlyFansUserID(), postID: getOnlyFansPostID() }) } function getStringBetween(string, prefix, suffix) { let begIndex = string.indexOf(prefix) if (begIndex == -1) return null else begIndex += prefix.length let endIndex = string.indexOf(suffix, begIndex) if (endIndex == -1) endIndex = undefined; let result = string.slice(begIndex, endIndex) return result } function getURLPathAfter(string) { let urlSplit = (window.location.hostname + window.location.pathname).split("/") let pathIndex = urlSplit.indexOf(string) + 1 if (pathIndex == 0) return null return urlSplit[pathIndex] } function getRedirectURL({domain, service, userID=null, postID=null} = {}) { let redirectURL = `https://${domain}/${service}` if (userID) { redirectURL += `/user/${userID}` if (postID) { redirectURL += `/post/${postID}` } } else return null return redirectURL }