Amazon eBook Mark

标记已购的电子书。

  1. // ==UserScript==
  2. // @name Amazon eBook Mark
  3. // @namespace https://greasyfork.org/users/34380
  4. // @version 20210724
  5. // @description 标记已购的电子书。
  6. // @match https://www.amazon.cn/*
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. const is_autosave = GM_getValue('is_autosave', false);
  15. const loc = location.href;
  16. let asins_s = GM_getValue('asins_saved', []);
  17. let asins_a = GM_getValue('asins_added', []);
  18.  
  19. if (loc.match(/\/kindle-dbs\/thankYouPage/) && is_autosave) {
  20. autoSaveBookPage();
  21. }
  22. setTimeout(() => {
  23. addBookButton();
  24. if (document.querySelector('#ng-app')) {
  25. saveAllBooksPage();
  26. } else if (loc.match(/\/dp\//) || loc.match(/\/product\//)) {
  27. viewBookDetailPage();
  28. }
  29. markBooksPage();
  30. }, 5000);
  31.  
  32. function addBookButton() {
  33. document.querySelector('#nav-xshop').insertAdjacentHTML('afterbegin', `
  34. <a id="display-panel" class="nav-a">添加书籍</a>
  35. `);
  36. document.querySelector('body').insertAdjacentHTML('afterbegin', `
  37. <div id="fixed-panel">
  38. <button id="refresh-page">刷新</button>
  39. <button id="btn-add-book">添加</button>
  40. <button id="btn-delete-book">删除</button>
  41. <input id="book-link" type="text" placeholder="输入书籍链接"></input>
  42. </div>
  43. `);
  44. document.querySelector('#display-panel').addEventListener('click', () => {
  45. document.querySelector('#fixed-panel').style.display = 'block';
  46. });
  47. document.querySelector('#refresh-page').addEventListener('click', () => {
  48. const nodes_added = document.querySelectorAll('.added');
  49. for (const node of nodes_added) {
  50. const href = node.parentNode.href;
  51. const matched = href.match(/dp\/(\w+)\//) || href.match(/\/product\/(\w+)\?/);
  52. if (!asins_a.includes(matched[1])) {
  53. node.classList.remove('added');
  54. }
  55. }
  56. markOwnedAdded(document);
  57. });
  58. document.querySelector('#btn-add-book').addEventListener('click', () => {
  59. const input_link = document.querySelector('#book-link');
  60. const asin = input_link.value.match(/\/dp\/(\w+)/) || input_link.value.match(/\/product\/(\w+)/);
  61. if (!asins_a.includes(asin[1])) {
  62. asins_a.push(asin[1]);
  63. GM_setValue('asins_added', asins_a);
  64. input_link.value = '';
  65. input_link.setAttribute('placeholder', '添加成功');
  66. } else {
  67. input_link.value = '';
  68. input_link.setAttribute('placeholder', '已添加');
  69. }
  70. });
  71. document.querySelector('#btn-delete-book').addEventListener('click', () => {
  72. const input_link = document.querySelector('#book-link');
  73. const asin = input_link.value.match(/\/dp\/(\w+)/) || input_link.value.match(/\/product\/(\w+)/);
  74. if (asins_a.includes(asin[1])) {
  75. asins_a.splice(asins_a.indexOf(asin[1]), 1);
  76. GM_setValue('asins_added', asins_a);
  77. input_link.value = '';
  78. input_link.setAttribute('placeholder', '删除成功');
  79. } else {
  80. input_link.value = '';
  81. input_link.setAttribute('placeholder', '已删除');
  82. }
  83. });
  84. }
  85.  
  86. function saveAllBooksPage() {
  87. document.querySelector('.myx-spacing-top-mini').insertAdjacentHTML('beforeend', `
  88. <div id="myx-panel" class="myx-float-left">
  89. <button id="save-asins" type="button" title="每页可显示 200 个,滚动到页尾加载,再点击保存按钮。">保存本页电子书</button>
  90. <button id="clear-asins-added" type="button">清空已添加书籍</button>
  91. 已保存 <span id="saved">${asins_s.length}</span> 本,添加已购 <span id="added">${asins_a.length}</span>本。
  92. <label id="label-autosave"><input id="cb-autosave" type="checkbox"></input>购买后自动保存</label>
  93. </div>
  94. `);
  95.  
  96. document.querySelector('#save-asins').addEventListener('click', () => {
  97. const items = document.querySelectorAll('.listItem_myx > div');
  98. for (const item of items) {
  99. const asin = item.getAttribute('name').replace('contentTabList_', '');
  100. if (!asins_s.includes(asin)) { asins_s.push(asin); }
  101. }
  102. GM_setValue('asins_saved', asins_s);
  103. document.querySelector('#saved').innerText = asins_s.length;
  104. });
  105.  
  106. document.querySelector('#clear-asins-added').addEventListener('click', () => {
  107. GM_setValue('asins_added', []);
  108. });
  109.  
  110. document.querySelector('#cb-autosave').checked = GM_getValue('is_autosave', false);
  111. document.querySelector('#cb-autosave').addEventListener('click', function () {
  112. GM_setValue('is_autosave', this.checked);
  113. });
  114. }
  115.  
  116. function autoSaveBookPage() {
  117. const asin_loc = loc.match(/asin=(\w+)&/)[1];
  118. if (!asins_s.includes(asin_loc)) {
  119. asins_s.push(asin_loc);
  120. GM_setValue('asins_saved', asins_s);
  121. document.title = '已购电子书自动保存成功。';
  122. }
  123. }
  124.  
  125. function viewBookDetailPage() {
  126. const asin_loc = loc.match(/\/dp\/(\w+)/) || loc.match(/\/product\/(\w+)/);
  127. if (!asins_s.includes(asin_loc[1])) {
  128. document.querySelector('#title').insertAdjacentHTML('afterbegin', `
  129. <label id="label-add-book"><input id="cb-add-book" class="nav-a" type="checkbox"></input>添加到已购书籍</label>
  130. `);
  131.  
  132. document.querySelector('#cb-add-book').checked = asins_a.includes(asin_loc[1]);
  133. document.querySelector('#cb-add-book').addEventListener('click', function () {
  134. if (this.checked) {
  135. asins_a.push(asin_loc[1]);
  136. GM_setValue('asins_added', asins_a);
  137. } else {
  138. asins_a.splice(asins_a.indexOf(asin_loc[1]), 1);
  139. GM_setValue('asins_added', asins_a);
  140. }
  141. });
  142. }
  143. }
  144.  
  145. function markBooksPage() {
  146. markOwnedAdded(document);
  147. const config = { attributes: false, childList: true, subtree: true };
  148. const threadMutation = (mutationsList) => {
  149. for (let mutation of mutationsList) {
  150. if (mutation.type == 'childList' && mutation.addedNodes.length > 0) {
  151. for (let node of mutation.addedNodes) {
  152. if (node.nodeName == 'DIV') { markOwnedAdded(node); }
  153. }
  154. }
  155. }
  156. };
  157. let container = document.querySelector("#a-page");
  158. let threadObserver = new MutationObserver(threadMutation);
  159. threadObserver.observe(container, config);
  160. }
  161.  
  162. function markOwnedAdded(node) {
  163. // 1/2 common top and bottom 3 search 4 recommend 5 bestseller 6 product
  164. const title_class1 = ['.acs-product-block__product-title',
  165. '.a-spacing-top-small > .a-link-normal',
  166. '.s-line-clamp-2 > .a-link-normal.a-text-normal'];
  167. const titles1 = node.querySelectorAll(title_class1.join(','));
  168. for (const title of titles1) {
  169. const href = title.href;
  170. const asin = href.match(/\/dp\/(\w+)/)[1] ;
  171. if (asins_s.includes(asin)) {
  172. title.querySelector('*').classList.add('owned');
  173. } else if (asins_a.includes(asin)) {
  174. title.querySelector('*').classList.add('added');
  175. }
  176. }
  177.  
  178. const title_class2 = ['.a-carousel-card > div > .a-link-normal:nth-last-of-type(1)',
  179. '.aok-inline-block.zg-item > .a-link-normal'];
  180. const titles2 = node.querySelectorAll(title_class2.join(','));
  181. for (const title of titles2) {
  182. const href = title.href;
  183. const asin = href.match(/\/dp\/(\w+)/)[1];
  184. if (asins_s.includes(asin)) {
  185. title.querySelector(':nth-child(2)').classList.add('owned');
  186. } else if (asins_a.includes(asin)) {
  187. title.querySelector(':nth-child(2)').classList.add('added');
  188. }
  189. }
  190.  
  191. const title_class3 = ['.a-box-group.a-spacing-top-micro >.a-size-small.a-link-normal'];
  192. const titles3 = node.querySelectorAll(title_class3.join(','));
  193. for (const title of titles3) {
  194. const href = title.href;
  195. const asin = href.match(/\/product\/(\w+)/)[1];
  196. if (asins_s.includes(asin)) {
  197. title.classList.add('owned');
  198. } else if (asins_a.includes(asin)) {
  199. title.classList.add('added');
  200. }
  201. }
  202. }
  203.  
  204. document.querySelector('head').insertAdjacentHTML('beforeend', `<style>
  205. #myx-panel { margin-left: 10px; }
  206. #myx-panel > button{ height: 31px; padding: 0 10px 0 11px; }
  207. #fixed-panel { display: none; position: fixed; left: 240px; bottom: 30px; z-index: 2; }
  208. #label-add-book { width: max-content; font-size: 14px; }
  209. #label-add-book:hover { background-color: #fc9b1a; }
  210. #cb-add-book { margin: revert; vertical-align: middle; position: revert; }
  211. #label-autosave { display: revert; padding: 5px; }
  212. #label-autosave:hover { background-color: #fc9b1a; }
  213. #cb-autosave { margin: revert; vertical-align: middle; position: revert; }
  214. .owned { background-color: #8bc34a; }
  215. .added { background-color: #fc9b1a; }
  216. </style>`);
  217. })();