// ==UserScript==
// @name PixivPrivateBookmarkButton
// @namespace sgthr7/monkey-script
// @version 0.0.1
// @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 o=document.createElement("style");o.textContent=t,document.head.append(o)})(" .ppbb-root{display:inline;-webkit-user-select:none;user-select:none}.ppbb-main{padding-right:13px}.ppbb-absolute{position:absolute;bottom:0;right:32px}.private-bookmark-button[data-v-c5b4a471]{color:inherit;font-size:large;font-family:inherit}.container[data-v-c5b4a471]{position:relative}.heart[data-v-c5b4a471]{font-size:200%}.heart-fill[data-v-c5b4a471]{color:inherit}.heart-outline[data-v-c5b4a471]{position:absolute;right:0;bottom:0;color:#000}.bookmarked[data-v-c5b4a471]{color:#ff4060}.lock[data-v-c5b4a471]{font-size:100%;position:absolute;right:-5px;bottom:1px} ");
(function (vue) {
'use strict';
const _withScopeId = (n) => (vue.pushScopeId("data-v-c5b4a471"), n = n(), vue.popScopeId(), n);
const _hoisted_1 = { class: "container" };
const _hoisted_2 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { class: "heart heart-outline" }, "♡", -1));
const _hoisted_3 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { class: "lock" }, "🔒️", -1));
const _sfc_main = /* @__PURE__ */ vue.defineComponent({
__name: "PrivateBookmarkButton",
props: {
artworkId: {
type: String,
required: true,
validator: (val) => {
const validatorRegex = /^\d+$/;
return validatorRegex.test(val);
}
},
relatedBookmarkButtonContainer: {
type: Element,
required: false
}
},
setup(__props) {
const props = __props;
const getBookmarkButton = () => {
var _a;
return (_a = props.relatedBookmarkButtonContainer) == null ? void 0 : _a.querySelector(":is(button, a:has(> svg))");
};
const isBookmarked = vue.ref(parseIsBookmarked());
function parseIsBookmarked() {
var _a;
const styleElementClass = "sc-j89e3c-1";
const styleElement = (_a = props.relatedBookmarkButtonContainer) == null ? void 0 : _a.querySelector(`.${styleElementClass}`);
const bookmarkedClassName = "bXjFLc";
return (styleElement == null ? void 0 : styleElement.classList.contains(bookmarkedClassName)) ?? false;
}
if (props.relatedBookmarkButtonContainer != null) {
const observer = new MutationObserver(() => {
isBookmarked.value = parseIsBookmarked();
});
observer.observe(props.relatedBookmarkButtonContainer, { subtree: true, childList: true, attributes: true, attributeFilter: ["class"] });
}
const bookmarkPageUrl = vue.computed(() => new URL(`https://www.pixiv.net/bookmark_add.php?type=illust&illust_id=${props.artworkId}`));
function privateBookmark() {
var _a;
(_a = getBookmarkButton()) == null ? void 0 : _a.click();
if (isBookmarked.value) {
return;
}
const bookmarkPageWindow = window.open(bookmarkPageUrl.value, "_blank", "popup,width=1,height=1,top=0,left=0");
const bookmarkPageAction = () => {
if (bookmarkPageWindow == null) {
throw new Error("Failed to get bookmark page window");
}
const bookmarkPageDocument = bookmarkPageWindow.document;
if (bookmarkPageDocument == null) {
throw new Error("Failed to get bookmark page document");
}
const form = bookmarkPageDocument.querySelector("section.bookmark-detail-unit>form");
if (form == null) {
throw new Error("Failed to find bookmark form");
}
const restrictRadio = form.elements.namedItem("restrict");
if (restrictRadio == null || !isRadioNodeList(restrictRadio)) {
throw new Error("Failed to get restrict radio button");
}
restrictRadio.value = "1";
const finishedEventName = "pagehide";
const onBookmarkedAction = () => {
bookmarkPageWindow.removeEventListener(finishedEventName, onBookmarkedAction);
bookmarkPageWindow.close();
};
bookmarkPageWindow.addEventListener(finishedEventName, onBookmarkedAction);
form.requestSubmit();
};
bookmarkPageWindow == null ? void 0 : bookmarkPageWindow.addEventListener("load", bookmarkPageAction);
}
function isRadioNodeList(target) {
return target instanceof RadioNodeList || target.toString() === RadioNodeList.prototype.toString();
}
return (_ctx, _cache) => {
return vue.openBlock(), vue.createElementBlock("button", {
type: "button",
class: "ppbb-button fgVkZi",
onClick: privateBookmark
}, [
vue.createElementVNode("div", _hoisted_1, [
vue.createElementVNode("span", {
class: vue.normalizeClass(["heart heart-fill", { bookmarked: isBookmarked.value }])
}, "♥", 2),
_hoisted_2,
vue.createTextVNode("️"),
_hoisted_3
])
]);
};
}
});
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-c5b4a471"]]);
function isElement(node) {
return node.nodeType === Node.ELEMENT_NODE;
}
const globalObserver = new MutationObserver((records, _observer) => {
records.forEach((record) => {
if (record.addedNodes.length <= 0) {
return;
}
if (Array.from(record.addedNodes).some((node) => isElement(node) && Array.from(node.classList).some((className) => className.startsWith("ppbb")))) {
return;
}
Array.from(record.addedNodes).filter(isElement).filter(
(el) => el.querySelectorAll("button.sc-kgq5hw-0").length === 1 && el.querySelector("div.ppbb-root") == null
).forEach(applyThumbnailArtwork);
if (record.addedNodes.length === 1) {
const maybeContainersOwner = record.addedNodes[0];
if (isElement(maybeContainersOwner)) {
const artworkContainersList = maybeContainersOwner.querySelectorAll(":is(ul, div.sc-1nhgff6-4) > :is(div, li):has(button.sc-kgq5hw-0)");
artworkContainersList.forEach((artworkContainers) => {
Array.from(artworkContainers.children).filter((el) => el.querySelector("div.ppbb-root") == null).forEach(applyThumbnailArtwork);
});
}
}
});
});
const globalObserverOption = {
childList: true,
subtree: true
};
function init() {
const initialArtworkContainers = document.querySelectorAll("div:has(a[data-gtm-value]):has(div:nth-child(2) button.sc-kgq5hw-0)");
initialArtworkContainers.forEach((el) => {
if (el.querySelectorAll("button.sc-kgq5hw-0").length === 1 && el.querySelector("div.ppbb-root") == null) {
applyThumbnailArtwork(el);
}
});
window.addEventListener("load", onLoad);
globalObserver.observe(document, globalObserverOption);
const titleObserver = new MutationObserver((records, _observer) => {
initMainArtwork();
});
const title = document.head.querySelector("title");
if (title != null) {
titleObserver.observe(title, {
childList: true,
subtree: true
});
}
}
function onLoad() {
initMainArtwork();
}
init();
function initMainArtwork() {
var _a;
const buttonContainer = document.querySelector("div.sc-181ts2x-3");
if (buttonContainer == null) {
return;
}
const url = new URL(window.location.href);
const artworkPageRegex = /^\/(?:en\/)?artworks\/(\d+)$/;
const regexResult = url.pathname.match(artworkPageRegex);
if (regexResult == null || regexResult.length <= 1) {
return;
}
const artworkId = regexResult[1];
const ppbbRoot = document.createElement("div");
ppbbRoot.classList.add("ppbb-root", "ppbb-main");
(_a = buttonContainer.parentNode) == null ? void 0 : _a.insertBefore(ppbbRoot, buttonContainer.nextElementSibling);
const app = vue.createApp(PrivateBookmarkButton, {
artworkId,
relatedBookmarkButtonContainer: buttonContainer
});
app.mount(ppbbRoot);
}
function applyThumbnailArtwork(target) {
var _a;
const artworkLink = target.querySelector("a[data-gtm-value]");
if (artworkLink == null) {
return;
}
const artworkId = artworkLink.getAttribute("data-gtm-value");
if (artworkId == null) {
return;
}
const button = target.querySelector("button");
if (button == null) {
return;
}
const buttonContainer = (_a = button.parentElement) == null ? void 0 : _a.parentElement;
if (buttonContainer == null) {
return;
}
const ppbbRoot = document.createElement("div");
ppbbRoot.classList.add("ppbb-root", "ppbb-absolute");
buttonContainer.appendChild(ppbbRoot);
const app = vue.createApp(PrivateBookmarkButton, {
artworkId,
relatedBookmarkButtonContainer: button.parentElement
});
app.mount(ppbbRoot);
}
})(Vue);