pixiv share helper

Convert sharing link to text with format: title | creator link

  1. // ==UserScript==
  2. // @name pixiv share helper
  3. // @name:zh-TW pixiv 分享助手
  4. // @namespace https://github.com/zica87/self-made-userscipts
  5. // @version 2.2.1
  6. // @description Convert sharing link to text with format: title | creator link
  7. // @description:zh-TW 將分享連結轉換為文字,格式:標題 | 作者 連結
  8. // @author zica
  9. // @match https://www.pixiv.net/*
  10. // @grant none
  11. // @license GPL-3.0
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. const container = generate_container();
  18. const [result, set_result] = generate_result();
  19. const input_wrapper = generate_input_wrapper();
  20. const paste_button = generate_paste_button();
  21. const box = generate_box();
  22.  
  23. const prompt_no_permission = set_result.bind(
  24. undefined,
  25. "no clipboard permission",
  26. "pink"
  27. );
  28.  
  29. paste_button.onclick = () => {
  30. navigator.clipboard.readText().then(
  31. (text) => {
  32. if (text.length === 0) {
  33. set_result("not text", "yellow");
  34. } else {
  35. copyText(process(text));
  36. }
  37. },
  38. () => {
  39. prompt_no_permission();
  40. }
  41. );
  42. };
  43.  
  44. box.oninput = (e) => {
  45. copyText(process(e.target.value));
  46. box.value = "";
  47. };
  48.  
  49. function copyText(text) {
  50. navigator.clipboard.writeText(text).then(
  51. () => {
  52. set_result("text copied", "lightgreen");
  53. },
  54. () => {
  55. prompt_no_permission();
  56. }
  57. );
  58. }
  59.  
  60. container.append(result);
  61. input_wrapper.append(paste_button);
  62. input_wrapper.append(box);
  63. container.append(input_wrapper);
  64. document.body.prepend(container);
  65.  
  66. let copyButton;
  67. let previousURL;
  68. const buttonId = "pixiv-share-helper";
  69.  
  70. const observer = new MutationObserver(() => {
  71. const pawoo_a = document.querySelector(
  72. '[data-gtm-category="gtm-share-by-pawoo-in-detail-click"]'
  73. );
  74. if (pawoo_a === null || document.getElementById(buttonId) !== null) {
  75. return;
  76. }
  77. const pawoo_li = pawoo_a.parentElement;
  78. const rawURL = pawoo_a.href;
  79. if (!rawURL?.startsWith("https://pawoo.net/share?text=")) {
  80. return;
  81. }
  82. if (copyButton === undefined) {
  83. copyButton = document.createElement("li");
  84. copyButton.classList = pawoo_li.classList;
  85. copyButton.id = buttonId;
  86. copyButton.style.cursor = "pointer";
  87.  
  88. const copyButtonText = document.createElement("span");
  89. copyButtonText.textContent = "copy text";
  90. copyButtonText.style.height = "24px";
  91. for (const currentClass of pawoo_a.classList) {
  92. // actually I don't know what it is
  93. if (!currentClass.startsWith("gtm")) {
  94. copyButtonText.classList.add(currentClass);
  95. }
  96. }
  97. copyButton.append(copyButtonText);
  98. }
  99. if (rawURL !== previousURL) {
  100. previousURL = rawURL;
  101. copyButton.onclick = copyText.bind(undefined, process(rawURL));
  102. }
  103. pawoo_li.after(copyButton);
  104. });
  105. observer.observe(document.body, {
  106. childList: true,
  107. subtree: true,
  108. });
  109.  
  110. function process(originalUrl) {
  111. const decoded = decodeURIComponent(originalUrl);
  112. // https://pawoo.net/share?text=お姉さん | 朱雷@Fantia更新中 #pixiv https://www.pixiv.net/artworks/108688782
  113. return decoded.substring(decoded.search("=") + 1).replace("#pixiv", "");
  114. // お姉さん | 朱雷@Fantia更新中 https://www.pixiv.net/artworks/108688782
  115. }
  116.  
  117. function generate_container() {
  118. const container = document.createElement("div");
  119. Object.assign(container.style, {
  120. position: "fixed",
  121. bottom: "5px",
  122. right: "5px",
  123. zIndex: 1,
  124. display: "flex",
  125. flexDirection: "column",
  126. alignItems: "flex-end",
  127. });
  128. return container;
  129. }
  130.  
  131. function generate_result() {
  132. const result_wrapper = document.createElement("div");
  133. Object.assign(result_wrapper.style, {
  134. marginBottom: "60px",
  135. display: "flex",
  136. justifyContent: "end",
  137. });
  138.  
  139. const result = document.createElement("p");
  140. Object.assign(result.style, {
  141. color: "black",
  142. padding: "1em",
  143. textAlign: "end",
  144. width: "max-content",
  145. });
  146.  
  147. let timeout_id;
  148. function set_result(text, backgroundColor) {
  149. Object.assign(result.style, {
  150. backgroundColor,
  151. });
  152. result.textContent = text;
  153. clearTimeout(timeout_id);
  154. result.style.display = "block";
  155. timeout_id = setTimeout(() => {
  156. result.style.display = "none";
  157. }, 5000);
  158. }
  159. result_wrapper.append(result);
  160. return [result_wrapper, set_result];
  161. }
  162.  
  163. function generate_input_wrapper() {
  164. const input_wrapper = document.createElement("div");
  165.  
  166. function hide() {
  167. input_wrapper.style.opacity = 0;
  168. }
  169. function show() {
  170. input_wrapper.style.opacity = 1;
  171. }
  172.  
  173. hide();
  174.  
  175. input_wrapper.addEventListener("mouseover", show);
  176. input_wrapper.addEventListener("dragover", show);
  177. input_wrapper.addEventListener("mouseleave", hide);
  178. input_wrapper.addEventListener("dragleave", hide);
  179. return input_wrapper;
  180. }
  181.  
  182. function generate_paste_button() {
  183. const paste_button = document.createElement("input");
  184. Object.assign(paste_button, {
  185. type: "button",
  186. value: "paste",
  187. });
  188. Object.assign(paste_button.style, {
  189. marginRight: "0.5em",
  190. });
  191. return paste_button;
  192. }
  193.  
  194. function generate_box() {
  195. const box = document.createElement("input");
  196. box.type = "text";
  197. Object.assign(box.style, {
  198. width: "5em",
  199. });
  200. return box;
  201. }
  202. })();