Image Webinfo

Extract JSON-/string-style info from Pixiv/Booru image pages.

Από την 31/07/2024. Δείτε την τελευταία έκδοση.

  1. // ==UserScript==
  2. // @name Image Webinfo
  3. // @name:zh-CN 图片网页信息提取
  4. // @version 0.1.0
  5. // @description Extract JSON-/string-style info from Pixiv/Booru image pages.
  6. // @description:zh-CN 将 Pixiv/Booru 图片页面中的信息以 JSON 或字符串形式导出。
  7. // @author wklchris
  8. // @match https://danbooru.donmai.us/posts/*
  9. // @match https://www.pixiv.net/artworks/*
  10. // @grant none
  11. // @license MIT
  12. // @namespace https://greasyfork.org/users/672201
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. // --- Customization ---
  18. // Specify returned keys when copy string
  19. let pixiv_str_keys = [
  20. // 'artist',
  21. // 'illust_id',
  22. 'artist_id',
  23. 'post_date',
  24. 'post_title'
  25. ];
  26. let booru_str_keys = [
  27. // 'artists',
  28. // 'copyrights',
  29. // 'characters',
  30. // 'general',
  31. 'booru_id',
  32. 'rating_level'
  33. ];
  34. // --- Main ---
  35. let artwork_data = {};
  36. let export_json_btn = document.createElement("button");
  37. export_json_btn.innerHTML = "Export WebInfo as JSON";
  38. let export_str_btn = document.createElement("button");
  39. export_str_btn.innerHTML = "Export WebInfo as String";
  40.  
  41. function logMessage(msg) {
  42. console.log("[ImageWebInfo] " + msg);
  43. }
  44. let popMessage = 'Image webinfo exported to clipboard.';
  45. function copyToClipboard(s) {
  46. try {
  47. navigator.clipboard.writeText(s);
  48. } catch (err) {
  49. popMessage = 'Image webinfo export failed.';
  50. logMessage(popMessage);
  51. }
  52. }
  53.  
  54. function attemptedQuerySelector(qs, callback) {
  55. // querySelector may not find the element when the website loads slowly.
  56. // Keep attempting until found or exceed the given maximum attempts.
  57. var attempts = 0, max_attempts = 20;
  58. var attemptCall = function() {
  59. var elem = document.querySelector(qs);
  60. if (elem) {
  61. console.log(`Found querySelector '${qs}' in ${attempts} attempts`);
  62. return callback(elem);
  63. } else {
  64. attempts++;
  65. if (attempts >= max_attempts) {
  66. console.warn(`Could not find: ${qs} within ${max_attempts} attempts`);
  67. } else {
  68. setTimeout(attemptCall, 4000*(attempts + 1)/max_attempts + 1000)
  69. }
  70. }
  71. };
  72. attemptCall();
  73. }
  74. // --- Pixiv posts ---
  75.  
  76. if (location.hostname.includes("pixiv")) {
  77. function getPixivInfo(as_JSON=true) {
  78. let data = {};
  79. // Get user
  80. let user_tag = document.querySelector('aside div[aria-haspopup="true"] > div > a');
  81. data['artist'] = user_tag.childNodes[0].innerText
  82. let split_user_url = user_tag.href.split('/');
  83. data['artist_id'] = split_user_url[split_user_url.length - 1];
  84. // Get post id
  85. let split_url = location.href.split('/');
  86. data['illust_id'] = split_url[split_url.length - 1];
  87. // Get upload date
  88. let timestr = document.getElementsByTagName('time')[0].innerText;
  89. let time_seps = {'year': '年', 'month': '月', 'day': '日'};
  90. let i_start = 0, i_end = -1, tmp_data = {};
  91. for (const [key, sep] of Object.entries(time_seps)) {
  92. i_start = i_end+1;
  93. i_end = timestr.search(sep);
  94. tmp_data[key] = timestr.substring(i_start, i_end);
  95. }
  96. data['post_date'] = `${tmp_data['year']}-${tmp_data['month'].padStart(2, 0)}-${tmp_data['day'].padStart(2, 0)}`;
  97. // Get post title
  98. data['post_title'] = document.querySelector('figcaption h1').innerText;
  99.  
  100. let s = "";
  101. if (as_JSON == true) {
  102. s = JSON.stringify(data)
  103. } else {
  104. s = pixiv_str_keys.map((x, i) => data[x]).join("\t");
  105. }
  106. copyToClipboard(s);
  107. return data;
  108. }
  109.  
  110. // Add buttons & their click functions
  111. let search_booru_btn = document.createElement("button");
  112. search_booru_btn.innerHTML = "Search artist on Booru";
  113.  
  114. attemptedQuerySelector('aside section button[data-click-label]', (exist_dom) => {
  115. exist_dom.parentNode.insertBefore(export_json_btn, exist_dom.nextSibling);
  116. exist_dom.parentNode.insertBefore(export_str_btn, export_json_btn.nextSibling);
  117. exist_dom.parentNode.insertBefore(search_booru_btn, export_str_btn.nextSibling);
  118. });
  119.  
  120. export_json_btn.onclick = function() {
  121. artwork_data = getPixivInfo(true);
  122. export_json_btn.style.background = 'lightgreen';
  123. export_str_btn.style.removeProperty("background");
  124. };
  125. export_str_btn.onclick = function() {
  126. artwork_data = getPixivInfo(false);
  127. export_str_btn.style.background = 'lightgreen';
  128. export_json_btn.style.removeProperty("background");
  129. };
  130. search_booru_btn.onclick = function() {
  131. if (!('artist_id' in artwork_data)) {
  132. artwork_data = getPixivInfo(false);
  133. }
  134. let _prefix = "https://danbooru.donmai.us/artists?commit=Search&search%5Border%5D=created_at&search%5Burl_matches%5D=https%3A%2F%2Fwww.pixiv.net%2Fusers%2F";
  135. let _url = _prefix + artwork_data["artist_id"];
  136. window.open(_url, "_blank");
  137. }
  138. }
  139.  
  140. // --- Booru posts ---
  141. if (location.hostname.includes("booru")) {
  142.  
  143. function getBooruInfo(as_JSON=true) {
  144. let data = {};
  145. let tag_list = document.getElementById("tag-list").querySelector("div");
  146.  
  147. function query_tags(ul_class, sep=", ") {
  148. // Get tag list by ul_class name. Join by sep if multiple.
  149. let query_str = `ul.${ul_class} a.search-tag`;
  150. let _tags = tag_list.querySelectorAll(query_str);
  151. return Array.from(_tags).map((x, i) => x.innerText).join(sep)
  152. }
  153. data["artists"] = query_tags("artist-tag-list");
  154. data["copyrights"] = query_tags("copyright-tag-list");
  155. data["characters"] = query_tags("character-tag-list");
  156. data["general"] = query_tags("general-tag-list");
  157.  
  158. // Get information (rating-levels, etc.)
  159. function query_by_id(dom_id, remove_str) {
  160. let _text = document.getElementById(dom_id).innerText;
  161. return _text.replace(remove_str, "").trim();
  162. }
  163. data["booru_id"] = query_by_id("post-info-id", "ID:");
  164. data["rating_level"] = query_by_id("post-info-rating", "Rating:");
  165.  
  166. let s = "";
  167. if (as_JSON == true) {
  168. s = JSON.stringify(data)
  169. } else {
  170. s = booru_str_keys.map((x, i) => data[x]).join("\t");
  171. }
  172. copyToClipboard(s);
  173. return data;
  174. }
  175.  
  176. // Add buttons
  177. attemptedQuerySelector('section[id="search-box"]', (exist_dom) => {
  178. exist_dom.parentNode.insertBefore(export_json_btn, exist_dom.nextSibling);
  179. exist_dom.parentNode.insertBefore(export_str_btn, export_json_btn.nextSibling);
  180. });
  181.  
  182. export_json_btn.onclick = function() {
  183. artwork_data = getBooruInfo(true);
  184. export_json_btn.style.background = 'lightgreen';
  185. export_str_btn.style.removeProperty("background");
  186. };
  187. export_str_btn.onclick = function() {
  188. artwork_data = getBooruInfo(false);
  189. export_str_btn.style.background = 'lightgreen';
  190. export_json_btn.style.removeProperty("background");
  191. };
  192. }
  193.  
  194. })();