关于油猴脚本

范例:使用油猴脚本及 Eagle API,开发 Pinterest 批量导入扩展

跨域访问 CORS 问题

如果你想要透过「油猴」等脚本扩展在网页中使用 Eagle API,请使用 GM_xmlhttpRequest 方法进行呼叫,即可避免跨域安全限制的问题。

  1. GM_xmlhttpRequest({
  2. url: EAGLE_IMPORT_API_URL,
  3. method: "POST",
  4. data: JSON.stringify({ items: images, folderId: pageInfo.folderId }),
  5. onload: function(response) {}
  6. });

完整代码:

  1. // ==UserScript==
  2. // @name Save Pinterest images to Eagle
  3. // @name:zh-CN 批量导入 Pinterest 图片到 Eagle
  4. // @name:zh-TW 批次導入 Pinterest 圖片到 Eagle
  5. // @name:ja-JP Pinterestの画像を Eagle に保存
  6. // @description Launch a script on Pinterest that automatically scrolls the page and converts all images on the page into large images (with links, names) to be added to the Eagle App.
  7. // @description:zh-CN 请确保你的网路环境可以正常访问 Pinterest,如果设备网路无法访问,此脚本将无法正常运作。在 Pinterest 画版页面启动脚本,此脚本会自动滚动页面,将页面中所有图片转换成大图(包含链接、名称),添加至 Eagle App。
  8. // @description:zh-TW 在 Pinterest 畫版頁面啓動腳本,此腳本會自動滾動頁面,將頁面中所有圖片轉換成大圖(包含鏈接、名稱),添加至 Eagle App。
  9. // @description:ja-JP Pinterestのボードページ上でスクリプトを起動すると、ページが自動的にスクロールし、ページ上のすべての画像を大きな画像(リンク、名前付き)に変換してEagleアプリに追加することができます。
  10. // @author Augus
  11. // @namespace https://eagle.cool/
  12. // @homepageURL https://eagle.cool/
  13. // @supportURL https://docs-cn.eagle.cool/
  14. // @icon https://cn.eagle.cool/favicon.png
  15. // @license MIT License
  16. // @match https://www.pinterest.com/*
  17. // @grant GM_xmlhttpRequest
  18. // @run-at context-menu
  19. // @date 06/16/2020
  20. // @modified 06/16/2020
  21. // @version 0.0.3
  22. // ==/UserScript==
  23. (function() {
  24. if (location.href.indexOf("pinterest.com") === -1) {
  25. alert("This script only works on pinterest.com.");
  26. return;
  27. }
  28. // Eagle API 服务器位置
  29. const EAGLE_SERVER_URL = "http://localhost:41595";
  30. const EAGLE_IMPORT_API_URL = `${EAGLE_SERVER_URL}/api/item/addFromURLs`;
  31. const EAGLE_CREATE_FOLDER_API_URL = `${EAGLE_SERVER_URL}/api/folder/create`;
  32. // Pinterest 当前图片、链接命名规则
  33. const SELECTOR_IMAGE = "[data-grid-item] a img[srcset]";
  34. const SELECTOR_LINK = "[data-grid-item] a";
  35. const SELECTOR_SPINNER = `[aria-label="Board Pins grid"]`;
  36. var startTime = Date.now(); // 开始滚动时间
  37. var scrollInterval; // 无限滚动,直到底部
  38. var lastScrollPos; // 上一次滚轴位置
  39. var retryCount = 0; // 目前重试次数
  40. var scrollDelay = 500; // 滚动页面延迟
  41. var retryThreshold = 4; // 无法滚动页面重试次数,当超过次数,表示到底部了
  42. var pageInfo = {
  43. imageCount: 0,
  44. imageSet: {},
  45. folderId: ""
  46. };
  47. // 创建文件夹
  48. var createFolder = function(folderName, callback) {
  49. GM_xmlhttpRequest({
  50. url: EAGLE_CREATE_FOLDER_API_URL,
  51. method: "POST",
  52. data: JSON.stringify({ folderName: folderName }),
  53. onload: function(response) {
  54. try {
  55. var result = JSON.parse(response.response);
  56. if (result.status === "success" && result.data && result.data.id) {
  57. callback(undefined, result.data);
  58. } else {
  59. callback(true);
  60. }
  61. } catch (err) {
  62. callback(true);
  63. }
  64. }
  65. });
  66. };
  67. // 滚动至页面顶端
  68. var scarollToTop = function() {
  69. window.scrollTo(0, 0);
  70. lastScrollPos = window.scrollY;
  71. };
  72. // 滚动至页面底端
  73. var scarollToBottom = function() {
  74. window.scrollTo(0, document.body.scrollHeight);
  75. // window.scrollTo(0, window.innerHeight);
  76. lastScrollPos = window.scrollY;
  77. };
  78. // 取得当前画面所有图片链接
  79. var getImgs = function() {
  80. var imgs = [];
  81. var imgElements = Array.from(document.querySelectorAll(SELECTOR_IMAGE));
  82. // 避免重复添加
  83. imgElements = imgElements.filter(function(elem) {
  84. var src = elem.src;
  85. if (!pageInfo.imageSet[src]) {
  86. pageInfo.imageSet[src] = true;
  87. return true;
  88. }
  89. return false;
  90. });
  91. var getLink = function(img) {
  92. var links = Array.from(document.querySelectorAll(SELECTOR_LINK));
  93. for (var i = 0; i < links.length; i++) {
  94. if (links[i].contains(img)) {
  95. return absolutePath(links[i].href);
  96. }
  97. }
  98. return "";
  99. };
  100. imgs = imgElements.map(function(elem, index) {
  101. pageInfo.imageCount++;
  102. return {
  103. name: elem.alt || "",
  104. url: getHighestResImg(elem) || elem.src, // 取得最大分辨率
  105. website: getLink(elem), // 取得图片链接
  106. modificationTime: startTime - pageInfo.imageCount // 强制设置时间,确保在 Eagle 顺序与 Pinterest 相同
  107. }
  108. });
  109. return imgs;
  110. };
  111. // 滚动页面并取得图片信息,发送至 Eagle App
  112. var fetchImages = function() {
  113. var currentScrollPos = window.scrollY;
  114. scarollToBottom();
  115. // 到底了
  116. if (lastScrollPos === currentScrollPos) {
  117. // 画面如果出现 Spinner 表示后面还有内容尚未载入完成
  118. if (!document.querySelector(SELECTOR_SPINNER)) {
  119. retryCount++;
  120. if (retryCount >= retryThreshold) {
  121. clearInterval(scrollInterval);
  122. alert(`Scan completed, a total of ${pageInfo.imageCount} images have been added to Eagle App.`);
  123. }
  124. }
  125. }
  126. // 还有内容
  127. else {
  128. retryCount = 0;
  129. var images = getImgs();
  130. addImagesToEagle(images);
  131. }
  132. }
  133. // 将图片添加至 Eagle
  134. var addImagesToEagle = function(images) {
  135. GM_xmlhttpRequest({
  136. url: EAGLE_IMPORT_API_URL,
  137. method: "POST",
  138. data: JSON.stringify({ items: images, folderId: pageInfo.folderId }),
  139. onload: function(response) {}
  140. });
  141. }
  142. function absolutePath(href) {
  143. if (href && href.indexOf(" ") > -1) {
  144. href = href.trim().split(" ")[0];
  145. }
  146. var link = document.createElement("a");
  147. link.href = href;
  148. return link.href;
  149. }
  150. function getHighestResImg(element) {
  151. if (element.getAttribute('srcset')) {
  152. let highResImgUrl = '';
  153. let maxRes = 0;
  154. let imgWidth, urlWidthArr;
  155. element.getAttribute('srcset').split(',').forEach((item) => {
  156. urlWidthArr = item.trim().split(' ');
  157. imgWidth = parseInt(urlWidthArr[1]);
  158. if (imgWidth > maxRes) {
  159. maxRes = imgWidth;
  160. highResImgUrl = urlWidthArr[0];
  161. }
  162. });
  163. return highResImgUrl;
  164. } else {
  165. return element.getAttribute('src');
  166. }
  167. }
  168. // 脚本开始
  169. scarollToTop();
  170. // 创建本次保存使用文件夹
  171. var folderName = document.querySelector("h1") && document.querySelector("h1").innerText || "Pinterest";
  172. createFolder(folderName, function(err, folder) {
  173. if (folder) {
  174. // 持续滚动列表,直到列表没有更多内容
  175. pageInfo.folderId = folder.id;
  176. scrollInterval = setInterval(fetchImages, 1000);
  177. } else {
  178. alert("An error has occurred or the Eagle app is not open.");
  179. }
  180. });
  181. })();