您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
pixiv.netで、非公開状態でブックマークするボタンを追加します
// ==UserScript== // @name PPrivateBookmarkButton // @namespace sgthr7/monkey-script // @version 0.0.3 // @author SGThr7 // @description pixiv.netで、非公開状態でブックマークするボタンを追加します // @description:en Add private bookmark button to pixiv.net // @license MIT // @match https://www.pixiv.net/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js // @grant GM_addStyle // ==/UserScript== (t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const e=document.createElement("style");e.textContent=t,document.head.append(e)})(" .ppbb-button[data-v-fd9e3920]{color:inherit;font-size:large;font-family:inherit;padding:0}.container[data-v-fd9e3920]{position:relative}.bookmarked[data-v-fd9e3920]{color:var(--3b0451ac)}.lock-icon[data-v-fd9e3920]{font-size:100%;position:absolute;right:-5px;bottom:1px}.ppbb-root{display:inline;-webkit-user-select:none;user-select:none}.ppbb-main{padding-right:13px}.ppbb-absolute{position:absolute;bottom:0;right:32px}:has(>.ppbb-root){display:flex} "); (function (vue) { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); function testNodeGen(nodeType) { return (node) => node instanceof nodeType; } function iterElementStack(target) { function* gen() { let current = target; while (current != null) { yield current; current = current.parentElement; } } return Iterator.from(gen()); } function makeLogGroupCollapsedFn(...data) { let isGrouping = false; function begin() { if (!isGrouping) { isGrouping = true; console.groupCollapsed(...data); } } function end() { if (isGrouping) { console.groupEnd(); } } function getState() { return isGrouping; } return { beginLogGroup: begin, endLogGroup: end, isGrouping: getState }; } function findStyles(selector) { return Iterator.from(document.styleSheets).map((sheet) => { try { const rules = sheet.cssRules; return rules; } catch (e) { if (e.name !== "SecurityError") throw e; return void 0; } }).filter((val) => val != null).flatMap((rules) => Iterator.from(rules)).filter((rule) => rule.constructor.name === CSSStyleRule.name).filter((rule) => rule.selectorText === selector).toArray(); } const baseUrl = new URL("https://www.pixiv.net/"); class PContent { // MARK: constructor constructor(id, type, baseFavButtonContainer, toggleFavButton) { __publicField(this, "_id"); __publicField(this, "_type"); __publicField(this, "_baseFavButtonContainer"); __publicField(this, "_toggleFavButton"); __publicField(this, "_toggleFavSvg"); __publicField(this, "_onChangedFav"); __publicField(this, "_isFavCache"); __publicField(this, "_observers"); this._id = id; this._type = type; this._baseFavButtonContainer = baseFavButtonContainer; this._toggleFavButton = toggleFavButton; this._toggleFavSvg = this._toggleFavButton.getElementsByTagName("svg").item(0); this._onChangedFav = /* @__PURE__ */ new Map(); this._isFavCache = this.testIsFav(); this._observers = []; { const observer = new MutationObserver((records) => { const newIsFavValue = Iterator.from(records.values()).map((record) => record.target).filter(testNodeGen(Element)).map((el) => PContent.testIsFav(el)).reduce((prev, cur) => cur, this._isFavCache); this.isFavCache = newIsFavValue; }); observer.observe(this._toggleFavButton, { subtree: true, childList: true, attributes: true, attributeFilter: ["class"] }); this._observers.push(observer); } } // MARK: accessor get id() { return this._id; } get type() { return this._type; } get url() { const relativeUrl = PContent.makeContentUrl(this.type, this.id); const fullUrl = new URL(relativeUrl, baseUrl); return fullUrl; } get favButton() { return this._baseFavButtonContainer; } get isFavCache() { return this._isFavCache; } set isFavCache(value) { if (this._isFavCache !== value) { this._isFavCache = value; Iterator.from(this._onChangedFav.values()).forEach((fn) => fn(this._isFavCache)); } } static tryFromFavButton(favButtonElement) { const contentLinkElement = PContent.findRelatedContentAnchorWithFavButton(favButtonElement); if (contentLinkElement == null) return void 0; const containerElement = favButtonElement.parentElement; if (containerElement == null) return void 0; const info = PContent.findInfoFromLinkElement(contentLinkElement); if (info == null) return void 0; const { type, id } = info; const testIsButton = testNodeGen(HTMLButtonElement); const toggleFavButton = PContent.testIsMainArtworkFavButton(favButtonElement) ? PContent.findFavButtonWithId(document, id) : favButtonElement; if (toggleFavButton == null || !testIsButton(toggleFavButton)) return void 0; return new PContent(id, type, favButtonElement, toggleFavButton); } // MARK: Edit contents toggleFav() { this._toggleFavButton.click(); } testIsFav() { return this._toggleFavSvg != null ? PContent.testIsFav(this._toggleFavSvg) : false; } registerOnChangedFav(fn) { const uuid = self.crypto.randomUUID(); if (this._onChangedFav.has(uuid)) { return void 0; } this._onChangedFav.set(uuid, fn); return uuid; } unregisterOnChangedFav(id) { if (this._onChangedFav.has(id)) { this._onChangedFav.delete(id); return true; } else { return false; } } // MARK: Static fn static get favButtonQuery() { return ':is(button,a[href^="/bookmark_add.php"]):has(> svg[width="32"][height="32"] path+path)'; } static findFavButton(container) { return container.querySelector(PContent.favButtonQuery); } static makeToggleFavButtonQuery(id) { return `div:has(> a img[src*="${id}"]) button`; } static findFavButtonWithId(container, id) { return container.querySelector(PContent.makeToggleFavButtonQuery(id)); } // static get mainContentFavButtonQuery() { // return ':is(button.gtm-main-bookmark,a[href^="/bookmark_add.php"]):has(> svg[width="32"][height="32"] path+path)' // } static testIsMainArtworkFavButton(favButton) { const isAnchor = testNodeGen(HTMLAnchorElement); return favButton.classList.contains("gtm-main-bookmark") || favButton.tagName === HTMLAnchorElement.toString() && isAnchor(favButton); } static get childContentAnchorQuery() { return ':is(a[href^="/artworks/"],a[href^="/novel/"],a[href*="i.pximg.net"]):has(img)'; } static findChildContentAnchor(container) { return container.querySelector(PContent.childContentAnchorQuery); } static findRelatedContentAnchorWithFavButton(favButton) { return iterElementStack(favButton).map(PContent.findChildContentAnchor).find((val) => val != null); } static findInfoFromLinkElement(imageAnchor) { var _a, _b, _c; const imageDom = imageAnchor.getElementsByTagName("img")[0]; const imageSrc = imageDom.src; if (imageSrc.includes("img-master") || imageSrc.includes("custom-thumb")) { const id = (_a = imageSrc.split("/").at(-1)) == null ? void 0 : _a.split("_").at(0); if (id != null) { return { type: "artworks", id }; } } else if (imageSrc.includes("novel-cover-master")) { const id = (_c = (_b = imageSrc.split("/").at(-1)) == null ? void 0 : _b.split("_").at(0)) == null ? void 0 : _c.slice("ci".length); if (id != null) { return { type: "novel", id }; } } return void 0; } static makeContentUrl(type, id) { switch (type) { case "artworks": { return new URL(`/artworks/${id}`); } case "novel": { return new URL(`/novel/show.php?id=${id}`); } } } static get favButtonColor() { return "rgb(255, 64, 96)"; } static testIsFav(el) { const favColor = PContent.favButtonColor; const isFav = Iterator.from(el.classList.values()).flatMap((className) => findStyles(`.${className}`)).map((rule) => rule.style.getPropertyValue("color")).filter((val) => val !== "").some((val) => val === favColor); return isFav; } } function collectContents(container) { return Iterator.from(collectFavButtons(container).values()).map(PContent.tryFromFavButton).filter((val) => val != null).toArray(); } function collectFavButtons(container) { return container.querySelectorAll(PContent.favButtonQuery); } async function bookmarkAsPrivate(id) { const url = new URL("https://www.pixiv.net/ajax/illusts/bookmarks/add"); const payload = { illust_id: id, restrict: 1, comment: "", tags: [] }; const req = new Request(url, { method: "POST", headers: { accept: "application/json", "content-type": "application/json; charset=UTF-8", "x-csrf-token": findToken() ?? "" }, body: JSON.stringify(payload), credentials: "same-origin" }); const res = await fetch(req); const body = await res.json(); if (!res.ok) { console.error("Response", body.message, res); } } function findToken() { var _a, _b, _c; const nextData = window.__NEXT_DATA__; if (nextData == null) return void 0; const serverStateRaw = (_b = (_a = nextData.props) == null ? void 0 : _a.pageProps) == null ? void 0 : _b.serverSerializedPreloadedState; if (serverStateRaw == null) return void 0; const serverState = JSON.parse(serverStateRaw); const token = (_c = serverState.api) == null ? void 0 : _c.token; return token; } const _hoisted_1 = { class: "container" }; const _hoisted_2 = ["innerHTML"]; const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "PrivateBookmarkButton", props: { content: { type: PContent, required: true } }, setup(__props) { vue.useCssVars((_ctx) => ({ "3b0451ac": vue.unref(favColor) })); const props = __props; const contentId = vue.computed(() => props.content.id); const originalFavSvg = vue.computed(() => props.content.favButton.querySelector("svg")); const favImg = vue.computed(() => { var _a; return ((_a = originalFavSvg.value) == null ? void 0 : _a.outerHTML) ?? "♥"; }); const favColor = PContent.favButtonColor; let onChangedFavHandler; const isFav = makeIsFavRef(); function makeIsFavRef() { const retRef = vue.customRef((track, trigger) => { onChangedFavHandler = props.content.registerOnChangedFav(trigger); return { get() { track(); return props.content.testIsFav(); }, // no setter set() { } }; }); return retRef; } function privateBookmark() { props.content.toggleFav(); setTimeout(() => { bookmarkAsPrivate(contentId.value).then(() => { }); }, 100); return; } vue.onBeforeUnmount(() => { if (onChangedFavHandler != null) { props.content.unregisterOnChangedFav(onChangedFavHandler); } }); return (_ctx, _cache) => { return !vue.unref(isFav) ? (vue.openBlock(), vue.createElementBlock("button", { key: 0, type: "button", class: "ppbb-button", onClick: privateBookmark }, [ vue.createElementVNode("div", _hoisted_1, [ vue.createElementVNode("div", { innerHTML: favImg.value }, null, 8, _hoisted_2), _cache[0] || (_cache[0] = vue.createElementVNode("span", { class: "lock-icon" }, "🔒️", -1)) ]) ])) : vue.createCommentVNode("", true); }; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const PrivateBookmarkButton = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-fd9e3920"]]); init(); function init() { console.groupCollapsed("Initial insert"); const contents = collectContents(document); Iterator.from(contents.values()).forEach(insertButton); const observerConfig = { childList: true, subtree: true }; const observer = new MutationObserver((records) => { const { beginLogGroup, endLogGroup, isGrouping } = makeLogGroupCollapsedFn("Detect new contents loaded", `len=${records.length}`); records.forEach((record) => { observer.disconnect(); Iterator.from(record.addedNodes.values()).filter(testNodeGen(Element)).map(PContent.findFavButton).filter((val) => val != null).map(PContent.tryFromFavButton).filter((val) => val != null).filter(() => { beginLogGroup(); return true; }).forEach(insertButton); observer.observe(document, observerConfig); }); if (isGrouping()) { console.log(records); } endLogGroup(); }); observer.observe(document, observerConfig); console.groupEnd(); } function insertButton(content) { console.groupCollapsed("insertButton", content.id); console.log("content", content); const favContainer = content.favButton.parentElement; if (favContainer == null) { console.error("Failed to find parent element of fav button", content.favButton); return; } const { appElement } = makeButton(content); favContainer.insertBefore(appElement, content.favButton); console.log("Insert private fav button", appElement); console.groupEnd(); } function makeButton(content) { const appElement = document.createElement("div"); appElement.classList.add("ppbb-root", ...content.favButton.classList); const app = vue.createApp(PrivateBookmarkButton, { content }); app.mount(appElement); return { app, appElement }; } })(Vue);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址