您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Kemono links everywhere!
当前为
// ==UserScript== // @name Kemono Browser // @namespace Violentmonkey Scripts // @version 1.1.1 // @description Kemono links everywhere! // @author zWolfrost // @license MIT // @match https://*.vimeo.com/* // @match https://*.kemono.su/* // @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://*.kemono.su/* // @icon https://kemono.su/static/favicon.ico // @grant GM.xmlHttpRequest // ==/UserScript== "use strict" const PROMPT_BTN_ID = "kemono-url-btn"; const UPDATE_POLLING_MS = 222; appendCSS(` #${PROMPT_BTN_ID} { position: fixed !important; display: none; bottom: 0px !important; right: 0px !important; z-index: 9999 !important; min-width: none !important; min-height: none !important; max-width: none !important; max-height: none !important; font-family: arial !important; font-weight: bold !important; font-size: 18px !important; line-height: normal !important; padding: 4px !important; margin: 12px !important; cursor: pointer !important; text-decoration: none !important; box-shadow: black 1px 1px 0px 0px, black 2px 2px 0px 0px, black 3px 3px 0px 0px, black 4px 4px 0px 0px !important; } #${PROMPT_BTN_ID}:hover { filter: brightness(90%) !important; } #${PROMPT_BTN_ID}:active { box-shadow: 0px 0px 0px 0px !important; bottom: -4px !important; right: -4px !important; } `); const PROMPT_BTN = document.createElement("a"); PROMPT_BTN.id = PROMPT_BTN_ID; PROMPT_BTN.target = "_blank"; document.body.prepend(PROMPT_BTN) setInterval(main, UPDATE_POLLING_MS); async function main() { const domain = window.location.hostname.split(".").slice(-2).join(".") const url = await Promise.resolve(domainMethods[domain]?.()); //console.log(url) if (url) { const state = await getCreatorState(url); promptURL(url, badgePresets[state]) } else { PROMPT_BTN.style.display = "none" } } const badgePresets = { found: {stateText: "Found", textColor: "white", backgroundColor: "green"}, incomplete: {stateText: "Incomplete", textColor: "black", backgroundColor: "gold"}, missing: {stateText: "Missing", textColor: "white", backgroundColor: "red"}, } function promptURL(url, {stateText, textColor, backgroundColor}) { PROMPT_BTN.href = url; PROMPT_BTN.innerText = "Kemono Creator: " + 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 }); }); switch (response.finalUrl) { case url: return "found"; case "https://kemono.su/artists": return "missing" default: return "incomplete"; } } } 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 } function getRedirectURLFromPatreon() { function getPatreonUserID() //html { const userIDPrefix = `"creator":{"data":{"id":"` const userIDSuffix = `"` return getStringBetween(document.documentElement.innerHTML, userIDPrefix, userIDSuffix) } function getPatreonPostID() //url { const postIDPrefix = `posts` const postIDInternalPrefix = `-` return getURLPathAfter(postIDPrefix)?.split(postIDInternalPrefix).at(-1) ?? null } return getRedirectURL({ service: "patreon", userID: getPatreonUserID(), postID: getPatreonPostID() }) } async function getRedirectURLFromFanbox() { function getFanboxCreatorID() //url { const creatorIDPrefix1st = `www.fanbox.cc` const creatorIDInternalPrefix1st = `@` const creatorIDSuffix2nd = `.` let creatorID = getURLPathAfter(creatorIDPrefix1st)?.split(creatorIDInternalPrefix1st)[1] ?? window.location.hostname.split(creatorIDSuffix2nd)[0] return creatorID == "www" ? null : creatorID } function getFanboxUserID() //html { const userIDPrefix = `creator/` const userIDSuffix = `/` return getStringBetween(document.documentElement.innerHTML, userIDPrefix, userIDSuffix) } async function getFanboxUserID2nd() //url (api) { const creatorID = getFanboxCreatorID() if (!creatorID) return null const creatorFetch = await new Promise(resolve => { GM.xmlHttpRequest({ url: `https://api.fanbox.cc/creator.get?creatorId=${creatorID}`, method: "GET", headers: { Origin: "https://fanbox.cc" }, onload: resolve }); }); return JSON.parse(creatorFetch.responseText)?.body?.user?.userId ?? null } function getFanboxPostID() //url { const postIDPrefix = `posts` return getURLPathAfter(postIDPrefix) } return getRedirectURL({ service: "fanbox", userID: getFanboxUserID() /* ?? await getFanboxUserID2nd() */, postID: getFanboxPostID() }) } function getRedirectURLFromPixiv() { function getPixivUserID() //url { const userIDPrefix = `users` return getURLPathAfter(userIDPrefix) } function getPixivUserID2nd() //html { const userIDPrefix = `users/` const userIDSuffix = `"` return getStringBetween(document.documentElement.innerHTML, userIDPrefix, userIDSuffix) } return getRedirectURL({ service: "fanbox", userID: getPixivUserID() ?? getPixivUserID2nd() }) } function getRedirectURLFromFantia() { function getFantiaUserID() //url { const userIDPrefix = `fanclubs` return getURLPathAfter(userIDPrefix) } function getFantiaUserID2nd() //html { const userIDPrefix = `fanclubs/` const userIDSuffix = `"` return getStringBetween(document.documentElement.innerHTML, userIDPrefix, userIDSuffix) } function getFantiaPostID() //url { const postIDPrefix = `posts` return getURLPathAfter(postIDPrefix) } return getRedirectURL({ service: "fantia", userID: getFantiaUserID() ?? getFantiaUserID2nd(), postID: getFantiaPostID() }) } function getRedirectURLFromBoosty() { function getBoostyUserID() //url { const userIDPrefix = `boosty.to` return getURLPathAfter(userIDPrefix) } function getBoostyPostID() //url { const postIDPrefix = `posts` return getURLPathAfter(postIDPrefix) } return getRedirectURL({ service: "boosty", userID: getBoostyUserID(), postID: getBoostyPostID() }) } function getRedirectURLFromDLsite() { function getDLsiteUserID() //html { const userIDPrefix = `maker_id/` const userIDSuffix = `.` return getStringBetween(document.documentElement.innerHTML, userIDPrefix, userIDSuffix) } function getDLsitePostID() //url { const postIDPrefix = `product_id` const postIDMatch = `\\d` let postID = getURLPathAfter(postIDPrefix).match(new RegExp(postIDMatch, "g")).join("") return postID ? "RE" + postID : null } return getRedirectURL({ service: "dlsite", userID: getDLsiteUserID(), postID: getDLsitePostID() }) } function getRedirectURLFromGumroad() { function getGumroadUserID() //html { const elementSelector = `script.js-react-on-rails-component` const userIDPrefix = `id":"` const userIDSuffix = `"` return getStringBetween(document.querySelector(elementSelector).innerText, userIDPrefix, userIDSuffix) } function getGumroadPostID() //url { const postIDPrefix = `l` return getURLPathAfter(postIDPrefix) } return getRedirectURL({ service: "gumroad", userID: getGumroadUserID(), postID: getGumroadPostID() }) } function getRedirectURLFromSubscribeStar() { function getSubScribeStarUserID() //html { const elementSelector = `img[data-type="avatar"]` return document.querySelector(elementSelector)?.alt.toLowerCase() ?? null } function getSubScribeStarPostID() //url { const postIDPrefix = `posts` return getURLPathAfter(postIDPrefix) } return getRedirectURL({ service: "subscribestar", userID: getSubScribeStarUserID(), postID: getSubScribeStarPostID() }) } 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({service, userID=null, postID=null} = {}) { let redirectURL = `https://kemono.su/${service}` if (userID) { redirectURL += `/user/${userID}` if (postID) { redirectURL += `/post/${postID}` } } else return null return redirectURL }