PixivPrivateBookmarkButton

pixiv.netで、非公開状態でブックマークするボタンを追加します

  1. // ==UserScript==
  2. // @name PixivPrivateBookmarkButton
  3. // @namespace sgthr7/monkey-script
  4. // @version 0.0.1
  5. // @author SGThr7
  6. // @description pixiv.netで、非公開状態でブックマークするボタンを追加します
  7. // @description:en Add private bookmark button to pixiv.net
  8. // @license MIT
  9. // @match https://www.pixiv.net/*
  10. // @require https://cdn.jsdelivr.net/npm/vue@3.4.38/dist/vue.global.prod.js
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (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} ");
  15.  
  16. (function (vue) {
  17. 'use strict';
  18.  
  19. const _withScopeId = (n) => (vue.pushScopeId("data-v-c5b4a471"), n = n(), vue.popScopeId(), n);
  20. const _hoisted_1 = { class: "container" };
  21. const _hoisted_2 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { class: "heart heart-outline" }, "♡", -1));
  22. const _hoisted_3 = /* @__PURE__ */ _withScopeId(() => /* @__PURE__ */ vue.createElementVNode("span", { class: "lock" }, "🔒️", -1));
  23. const _sfc_main = /* @__PURE__ */ vue.defineComponent({
  24. __name: "PrivateBookmarkButton",
  25. props: {
  26. artworkId: {
  27. type: String,
  28. required: true,
  29. validator: (val) => {
  30. const validatorRegex = /^\d+$/;
  31. return validatorRegex.test(val);
  32. }
  33. },
  34. relatedBookmarkButtonContainer: {
  35. type: Element,
  36. required: false
  37. }
  38. },
  39. setup(__props) {
  40. const props = __props;
  41. const getBookmarkButton = () => {
  42. var _a;
  43. return (_a = props.relatedBookmarkButtonContainer) == null ? void 0 : _a.querySelector(":is(button, a:has(> svg))");
  44. };
  45. const isBookmarked = vue.ref(parseIsBookmarked());
  46. function parseIsBookmarked() {
  47. var _a;
  48. const styleElementClass = "sc-j89e3c-1";
  49. const styleElement = (_a = props.relatedBookmarkButtonContainer) == null ? void 0 : _a.querySelector(`.${styleElementClass}`);
  50. const bookmarkedClassName = "bXjFLc";
  51. return (styleElement == null ? void 0 : styleElement.classList.contains(bookmarkedClassName)) ?? false;
  52. }
  53. if (props.relatedBookmarkButtonContainer != null) {
  54. const observer = new MutationObserver(() => {
  55. isBookmarked.value = parseIsBookmarked();
  56. });
  57. observer.observe(props.relatedBookmarkButtonContainer, { subtree: true, childList: true, attributes: true, attributeFilter: ["class"] });
  58. }
  59. const bookmarkPageUrl = vue.computed(() => new URL(`https://www.pixiv.net/bookmark_add.php?type=illust&illust_id=${props.artworkId}`));
  60. function privateBookmark() {
  61. var _a;
  62. (_a = getBookmarkButton()) == null ? void 0 : _a.click();
  63. if (isBookmarked.value) {
  64. return;
  65. }
  66. const bookmarkPageWindow = window.open(bookmarkPageUrl.value, "_blank", "popup,width=1,height=1,top=0,left=0");
  67. const bookmarkPageAction = () => {
  68. if (bookmarkPageWindow == null) {
  69. throw new Error("Failed to get bookmark page window");
  70. }
  71. const bookmarkPageDocument = bookmarkPageWindow.document;
  72. if (bookmarkPageDocument == null) {
  73. throw new Error("Failed to get bookmark page document");
  74. }
  75. const form = bookmarkPageDocument.querySelector("section.bookmark-detail-unit>form");
  76. if (form == null) {
  77. throw new Error("Failed to find bookmark form");
  78. }
  79. const restrictRadio = form.elements.namedItem("restrict");
  80. if (restrictRadio == null || !isRadioNodeList(restrictRadio)) {
  81. throw new Error("Failed to get restrict radio button");
  82. }
  83. restrictRadio.value = "1";
  84. const finishedEventName = "pagehide";
  85. const onBookmarkedAction = () => {
  86. bookmarkPageWindow.removeEventListener(finishedEventName, onBookmarkedAction);
  87. bookmarkPageWindow.close();
  88. };
  89. bookmarkPageWindow.addEventListener(finishedEventName, onBookmarkedAction);
  90. form.requestSubmit();
  91. };
  92. bookmarkPageWindow == null ? void 0 : bookmarkPageWindow.addEventListener("load", bookmarkPageAction);
  93. }
  94. function isRadioNodeList(target) {
  95. return target instanceof RadioNodeList || target.toString() === RadioNodeList.prototype.toString();
  96. }
  97. return (_ctx, _cache) => {
  98. return vue.openBlock(), vue.createElementBlock("button", {
  99. type: "button",
  100. class: "ppbb-button fgVkZi",
  101. onClick: privateBookmark
  102. }, [
  103. vue.createElementVNode("div", _hoisted_1, [
  104. vue.createElementVNode("span", {
  105. class: vue.normalizeClass(["heart heart-fill", { bookmarked: isBookmarked.value }])
  106. }, "♥", 2),
  107. _hoisted_2,
  108. vue.createTextVNode("️"),
  109. _hoisted_3
  110. ])
  111. ]);
  112. };
  113. }
  114. });
  115. const _export_sfc = (sfc, props) => {
  116. const target = sfc.__vccOpts || sfc;
  117. for (const [key, val] of props) {
  118. target[key] = val;
  119. }
  120. return target;
  121. };
  122. const PrivateBookmarkButton = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-c5b4a471"]]);
  123. function isElement(node) {
  124. return node.nodeType === Node.ELEMENT_NODE;
  125. }
  126. const globalObserver = new MutationObserver((records, _observer) => {
  127. records.forEach((record) => {
  128. if (record.addedNodes.length <= 0) {
  129. return;
  130. }
  131. if (Array.from(record.addedNodes).some((node) => isElement(node) && Array.from(node.classList).some((className) => className.startsWith("ppbb")))) {
  132. return;
  133. }
  134. Array.from(record.addedNodes).filter(isElement).filter(
  135. (el) => el.querySelectorAll("button.sc-kgq5hw-0").length === 1 && el.querySelector("div.ppbb-root") == null
  136. ).forEach(applyThumbnailArtwork);
  137. if (record.addedNodes.length === 1) {
  138. const maybeContainersOwner = record.addedNodes[0];
  139. if (isElement(maybeContainersOwner)) {
  140. const artworkContainersList = maybeContainersOwner.querySelectorAll(":is(ul, div.sc-1nhgff6-4) > :is(div, li):has(button.sc-kgq5hw-0)");
  141. artworkContainersList.forEach((artworkContainers) => {
  142. Array.from(artworkContainers.children).filter((el) => el.querySelector("div.ppbb-root") == null).forEach(applyThumbnailArtwork);
  143. });
  144. }
  145. }
  146. });
  147. });
  148. const globalObserverOption = {
  149. childList: true,
  150. subtree: true
  151. };
  152. function init() {
  153. const initialArtworkContainers = document.querySelectorAll("div:has(a[data-gtm-value]):has(div:nth-child(2) button.sc-kgq5hw-0)");
  154. initialArtworkContainers.forEach((el) => {
  155. if (el.querySelectorAll("button.sc-kgq5hw-0").length === 1 && el.querySelector("div.ppbb-root") == null) {
  156. applyThumbnailArtwork(el);
  157. }
  158. });
  159. window.addEventListener("load", onLoad);
  160. globalObserver.observe(document, globalObserverOption);
  161. const titleObserver = new MutationObserver((records, _observer) => {
  162. initMainArtwork();
  163. });
  164. const title = document.head.querySelector("title");
  165. if (title != null) {
  166. titleObserver.observe(title, {
  167. childList: true,
  168. subtree: true
  169. });
  170. }
  171. }
  172. function onLoad() {
  173. initMainArtwork();
  174. }
  175. init();
  176. function initMainArtwork() {
  177. var _a;
  178. const buttonContainer = document.querySelector("div.sc-181ts2x-3");
  179. if (buttonContainer == null) {
  180. return;
  181. }
  182. const url = new URL(window.location.href);
  183. const artworkPageRegex = /^\/(?:en\/)?artworks\/(\d+)$/;
  184. const regexResult = url.pathname.match(artworkPageRegex);
  185. if (regexResult == null || regexResult.length <= 1) {
  186. return;
  187. }
  188. const artworkId = regexResult[1];
  189. const ppbbRoot = document.createElement("div");
  190. ppbbRoot.classList.add("ppbb-root", "ppbb-main");
  191. (_a = buttonContainer.parentNode) == null ? void 0 : _a.insertBefore(ppbbRoot, buttonContainer.nextElementSibling);
  192. const app = vue.createApp(PrivateBookmarkButton, {
  193. artworkId,
  194. relatedBookmarkButtonContainer: buttonContainer
  195. });
  196. app.mount(ppbbRoot);
  197. }
  198. function applyThumbnailArtwork(target) {
  199. var _a;
  200. const artworkLink = target.querySelector("a[data-gtm-value]");
  201. if (artworkLink == null) {
  202. return;
  203. }
  204. const artworkId = artworkLink.getAttribute("data-gtm-value");
  205. if (artworkId == null) {
  206. return;
  207. }
  208. const button = target.querySelector("button");
  209. if (button == null) {
  210. return;
  211. }
  212. const buttonContainer = (_a = button.parentElement) == null ? void 0 : _a.parentElement;
  213. if (buttonContainer == null) {
  214. return;
  215. }
  216. const ppbbRoot = document.createElement("div");
  217. ppbbRoot.classList.add("ppbb-root", "ppbb-absolute");
  218. buttonContainer.appendChild(ppbbRoot);
  219. const app = vue.createApp(PrivateBookmarkButton, {
  220. artworkId,
  221. relatedBookmarkButtonContainer: button.parentElement
  222. });
  223. app.mount(ppbbRoot);
  224. }
  225.  
  226. })(Vue);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址