Library Helper

A userscript that display links between different libraries and book stores.

当前为 2020-03-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Library Helper
  3. // @namespace https://github.com/chihchun
  4. // @version 1.7
  5. // @description A userscript that display links between different libraries and book stores.
  6. // @author Rex Tsai <rex.cc.tsai@gmail.com>
  7. // @match http://book.tpml.edu.tw/webpac/bookDetail.do*
  8. // @match https://book.douban.com/subject/*
  9. // @match https://books.google.com.tw/books*
  10. // @match https://books.google.com/books*
  11. // @match https://books.google.fr/books*
  12. // @match https://play.google.com/store/books/details/*
  13. // @match https://play.google.com/store/books/details?id=*
  14. // @match https://share.readmoo.com/book/*
  15. // @match https://readmoo.com/book/*
  16. // @match https://www.amazon.cn/dp/*
  17. // @match https://www.amazon.cn/gp/product/*
  18. // @match https://www.amazon.com/*/dp/*
  19. // @match https://www.amazon.com/gp/product/*
  20. // @match https://www.books.com.tw/products/*
  21. // @match https://www.goodreads.com/book/show/*
  22. // @match https://www.kobo.com/*/ebook*
  23. // @match https://www.taaze.tw/goods/*
  24. // @match https://www.taaze.tw/usedList.html?oid=*
  25. // @match https://www.babelio.com/livres/*
  26. // @match http://bibliotheque.ville-bobigny.fr/detail-d-une-notice/notice/*
  27. // @match https://webpac.tphcc.gov.tw/webpac/content.cfm*
  28. // @grant none
  29. // @require https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.13.1/js-yaml.min.js
  30. // @run-at document-idle
  31. // @license MIT; https://github.com/chihchun/library-helper/blob/master/LICENSE
  32. // @copyright 2019, chihchun (https://github.com/chihchun)
  33. // @supportURL https://github.com/chihchun/library-helper/issues
  34. // ==/UserScript==
  35.  
  36. (function() {
  37. 'use strict';
  38.  
  39. var metadata_yaml = `
  40. amazon.com:
  41. matches:
  42. - https://www.amazon.com/gp/product/*
  43. type: 'XPATH'
  44. metadata:
  45. title: "//span[@id='ebooksProductTitle']"
  46. authors: "//span[contains(@data-a-popover, 'Author Dialog')]/a"
  47. asin: "//form[@id='sendSample']/input[@name='ASIN.0']/@value"
  48. amazon.com/dp:
  49. matches:
  50. - https://www.amazon.com/*
  51. type: 'XPATH'
  52. metadata:
  53. title: "//span[@id='ebooksProductTitle']"
  54. authors: //span[contains(@class, 'author')]/a
  55. asin: "//form[@id='sendSample']/input[@name='ASIN.0']/@value"
  56.  
  57. amazon.cn:
  58. matches:
  59. - "https://www.amazon.cn/gp/product/*"
  60. - "https://www.amazon.cn/dp/*"
  61. type: 'XPATH'
  62. metadata:
  63. title: "//span[@id='ebooksProductTitle']"
  64. authors: "//span[contains(@class,'author')]/a"
  65. asin: "//form[@id='sendSample']/input[@name='ASIN.0']/@value"
  66.  
  67. babelio.com:
  68. matches:
  69. - "https://www.babelio.com/livres/*"
  70. type: 'XPATH'
  71. metadata:
  72. title: "//h1"
  73. authors: "//span[@itemprop='name']"
  74.  
  75. bibliotheque.ville-bobigny.fr:
  76. matches:
  77. - "http://bibliotheque.ville-bobigny.fr/detail-d-une-notice/notice/*"
  78. type: 'XPATH'
  79. metadata:
  80. title: "//title"
  81. authros: "//a[contains(@class, 'ntc-link-auteur')]"
  82.  
  83. books.com.tw:
  84. matches:
  85. - "https://www.books.com.tw/products/*"
  86. type: 'XPATH'
  87. metadata:
  88. title: "//h1"
  89. origtitle: "//h2/a[contains(@href,'https://search.books.com.tw/search/query/cat/all/key')]"
  90. isbn: "//li[contains(text(),'ISBN')]"
  91. price: "//ul[@class='price']/li/em"
  92. sellingprice: "//b[@itemprop='price']"
  93. authors: "//a[contains(@href,'adv_author')]"
  94. publishdate: "//li[contains(text(),'出版日期')]"
  95.  
  96. books.google.com.tw:
  97. matches:
  98. - "https://books.google.com.tw/books/*"
  99. - "https://books.google.com/books/*"
  100. - "https://books.google.fr/books/*"
  101. type: 'XPATH'
  102. metadata:
  103. title: "//meta[@property='og:title']/@content"
  104. authors: "//a[contains(@href,'q=inauthor')]"
  105.  
  106. goodreads.com:
  107. matches:
  108. - "https://www.goodreads.com/book/show/*"
  109. type: 'XPATH'
  110. metadata:
  111. title: "//meta[@property='og:title']/@content"
  112. authors: "//a[@class='authorName']/span[@itemprop='name']"
  113. isbn: "//meta[@property='books:isbn']/@content"
  114. rating: "//span[@itemprop='ratingValue']"
  115.  
  116. kobo.com:
  117. matches:
  118. - "https://www.kobo.com/*"
  119. type: 'JSON-LD'
  120. metadata:
  121. title: '//span[@class="title product-field"]'
  122. authors: '//a[@class="contributor-name"]'
  123. origtitle: "//span[contains(@class, 'subtitle')]"
  124.  
  125. play.google.com:
  126. matches:
  127. - "https://play.google.com/store/books/details/*"
  128. - "https://play.google.com/store/books/details?id=*"
  129. type: 'JSON-LD'
  130. metadata:
  131.  
  132. readmoo.com:
  133. matches:
  134. - "https://share.readmoo.com/book/*"
  135. - "https://readmoo.com/book/*"
  136. type: 'XPATH'
  137. metadata:
  138. title: "//h2"
  139. isbn: "//span[@itemprop='ISBN']"
  140. authors: "//span[@itemprop='name']/a"
  141.  
  142. taaze.tw:
  143. matches:
  144. - "https://www.taaze.tw/goods/*"
  145. type: 'XPATH'
  146. metadata:
  147. title: "//div[contains(@class, 'mBody')]//h1"
  148. origtitle: "//div[contains(@class, 'mBody')]//h2"
  149. isbn: "//meta[@property='books:isbn']/@content"
  150. authors: "//div[@class='authorBrand']//a[contains(@href,'rwd_searchResult.html?keyType%5B%5D=2')]"
  151. taaze.tw/used:
  152. matches:
  153. - "https://www.taaze.tw/usedList.html*"
  154. type: 'XPATH'
  155. metadata:
  156. origtitle: "//div[contains(@class, 'hide')]//div[@class='title-next']"
  157. authors: "//a[contains(@href,'rwd_searchResult.html?keyType%5B%5D=2')]"
  158.  
  159. tpml.edu.tw:
  160. matches:
  161. - "http://book.tpml.edu.tw/webpac/bookDetail.do*"
  162. type: 'XPATH'
  163. metadata:
  164. title: "//h3"
  165. authors: "//a[contains(@href,'search_field=PN')]"
  166.  
  167. webpac.tphcc.gov.tw:
  168. matches:
  169. - "https://webpac.tphcc.gov.tw/webpac/content.cfm*"
  170. type: 'XPATH'
  171. metadata:
  172. title: "//h2"
  173. authors: "//div[@class='detail simple']/p[1]"
  174. isbn: "//div[@class='detail simple']/p[4]"
  175.  
  176. `;
  177.  
  178. var search_yaml = `
  179. 博客來:
  180. url: "https://search.books.com.tw/search/query/key/"
  181. languages:
  182. - "en"
  183. - "en-US"
  184. - "zh"
  185. - "zh-TW"
  186. - "zh-HK"
  187. - "zh-CN"
  188.  
  189. Kobo:
  190. url: "https://www.kobo.com/tw/zh/search?query="
  191. languages:
  192. - "en"
  193. - "en-US"
  194. - "zh"
  195. - "zh-TW"
  196. - "zh-HK"
  197. - "zh-CN"
  198. - "fr"
  199. - "fr-ca"
  200. - "fr-fr"
  201.  
  202. GooglePlay:
  203. url: "https://play.google.com/store/search?c=books&q="
  204. languages:
  205. - "en"
  206. - "en-US"
  207. - "zh"
  208. - "zh-TW"
  209. - "zh-HK"
  210. - "zh-CN"
  211. - "fr"
  212. - "fr-ca"
  213. - "fr-fr"
  214.  
  215. AmazonCN:
  216. url: "https://www.amazon.cn/s?rh=n%3A116169071&k="
  217. languages:
  218. - "en"
  219. - "en-US"
  220. - "zh"
  221. - "zh-TW"
  222. - "zh-HK"
  223. - "zh-CN"
  224.  
  225. 豆瓣:
  226. url: "https://search.douban.com/book/subject_search?search_text="
  227. languages:
  228. - "zh"
  229. - "zh-TW"
  230. - "zh-HK"
  231. - "zh-CN"
  232.  
  233. Goodreads:
  234. url: "https://www.goodreads.com/search?q="
  235. languages:
  236. - "en"
  237. - "en-US"
  238. - "zh"
  239. - "zh-TW"
  240. - "zh-HK"
  241. - "zh-CN"
  242. - "fr"
  243. - "fr-ca"
  244. - "fr-fr"
  245.  
  246. Google:
  247. url: "https://www.google.com/search?tbm=bks&q="
  248. languages:
  249. - "en"
  250. - "en-US"
  251. - "zh"
  252. - "zh-TW"
  253. - "zh-HK"
  254. - "zh-CN"
  255. - "fr"
  256. - "fr-ca"
  257. - "fr-fr"
  258.  
  259. 北市圖書館:
  260. url: "http://book.tpml.edu.tw/webpac/bookSearchList.do?search_field=FullText&search_input="
  261. languages:
  262. - "en"
  263. - "en-US"
  264. - "zh"
  265. - "zh-TW"
  266. - "zh-HK"
  267. - "zh-CN"
  268.  
  269. 北市圖書館Hyread:
  270. url: "https://tpml.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  271. languages:
  272. - "zh"
  273. - "zh-TW"
  274. - "zh-HK"
  275.  
  276. 新北市圖書館:
  277. url: "https://webpac.tphcc.gov.tw/webpac/search.cfm?m=ss&t0=k&c0=and&k0="
  278. languages:
  279. - "en"
  280. - "en-US"
  281. - "zh"
  282. - "zh-TW"
  283. - "zh-HK"
  284. - "zh-CN"
  285.  
  286. 新北市Hyread:
  287. url: "https://tphcc.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  288. languages:
  289. - "zh"
  290. - "zh-TW"
  291. - "zh-HK"
  292.  
  293. 國立臺灣圖書館Hyread:
  294. url: "https://ntledu.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  295. languages:
  296. - "zh"
  297. - "zh-TW"
  298. - "zh-HK"
  299.  
  300. 台灣雲端書庫:
  301. url: "https://www.ebookservice.tw/#search/"
  302. languages:
  303. - "zh"
  304. - "zh-TW"
  305. - "zh-HK"
  306.  
  307. 讀冊:
  308. url: "https://www.taaze.tw/rwd_searchResult.html?keyword%5B%5D="
  309. languages:
  310. - "zh"
  311. - "zh-TW"
  312. - "zh-HK"
  313. - "zh-CN"
  314.  
  315. Readmoo:
  316. url: "https://share.readmoo.com/search/keyword?q="
  317. languages:
  318. - "zh"
  319. - "zh-TW"
  320. - "zh-HK"
  321. - "zh-CN"
  322.  
  323. Amazon:
  324. url: "https://www.amazon.com/s?i=digital-text&k="
  325. languages:
  326. - "en"
  327. - "en-US"
  328. - "zh"
  329. - "zh-TW"
  330. - "zh-HK"
  331. - "zh-CN"
  332. - "fr"
  333. - "fr-ca"
  334. - "fr-fr"
  335.  
  336. Babelio:
  337. url: "https://www.babelio.com/resrecherche.php?Recherche="
  338. languages:
  339. - "fr"
  340. - "fr-ca"
  341. - "fr-fr"
  342.  
  343. Bobigny:
  344. url: "http://bibliotheque.ville-bobigny.fr/recherche-catalogue/recherche-simple/simple/Mots%20Notice/0/"
  345. languages:
  346. - "fr"
  347. - "fr-ca"
  348. - "fr-fr"
  349. `;
  350.  
  351. var keywords = ['title', 'authors', 'origtitle', 'isbn', 'asin'];
  352.  
  353.  
  354. parse_metadata();
  355.  
  356. function parse_metadata() {
  357. var rules = jsyaml.load(metadata_yaml);
  358. var data = {};
  359.  
  360. // parse the ld+json
  361. var jsons = evaluate('//script[@type="application/ld+json"]');
  362. if(jsons.length > 0) {
  363. jsons.forEach(function(json) {
  364. var ld = JSON.parse(json);
  365. console.debug(ld);
  366. if(ld['@type'] == "Book") {
  367. data['title'] = [ld['name']];
  368.  
  369. if(ld['isbn'] != undefined) {
  370. data['isbn']= [ld['isbn']];
  371. }
  372.  
  373. if(ld['workExample'] != undefined && ld['workExample']['isbn'] != undefined ) {
  374. data['isbn']= [ld['workExample']['isbn']];
  375. }
  376.  
  377. data['authors'] = [];
  378. if(ld['author'] != undefined) {
  379. if(Array.isArray(ld['author'])) {
  380. ld['author'].forEach(function (author) {
  381. data['authors'].push(author['name']);
  382. })
  383. } else {
  384. data['authors'].push(ld['author']['name']);
  385. }
  386. }
  387. }
  388. })
  389. }
  390.  
  391. // parse the metadata by xpath
  392. for (var domain in rules) {
  393. rules[domain]['matches'].forEach(function (match) {
  394. if(document.URL.match(match)) {
  395. var metadata = rules[domain]['metadata'];
  396. for (var key in metadata) {
  397. data[key] = evaluate(metadata[key]);
  398. }
  399. return;
  400. }
  401. })
  402. }
  403.  
  404. // Links to other websites
  405. if(Object.keys(data).length > 0) {
  406. console.debug(data);
  407. var dialog = inject();
  408. var urlsforsearch = jsyaml.load(search_yaml);
  409.  
  410. for (var service in urlsforsearch) {
  411. if(!isPreferLang(urlsforsearch[service]['languages'])) {
  412. continue;
  413. }
  414.  
  415. var url = urlsforsearch[service]['url'];
  416. var html = `<div>${service}: `;
  417. keywords.forEach(function(key) {
  418. if(data[key] != undefined) {
  419. data[key].forEach(function(val) {
  420. var href = url + encodeURI(val);
  421. html += `<a href="${href}" target="_blank">${val}</a> `;
  422. })
  423. }
  424. })
  425. html += "</div>";
  426. dialog.insertAdjacentHTML('beforeend', html)
  427. }
  428. }
  429. }
  430.  
  431. function isPreferLang(offers) {
  432. var languages = window.navigator.userLanguage || window.navigator.languages || [window.navigator.language];
  433. var ret = false;
  434. languages.forEach(function(lang) {
  435. if(offers.includes(lang)) {
  436. ret = true;
  437. }
  438. })
  439. return ret;
  440. }
  441.  
  442. function inject () {
  443. var div = document.createElement('div');
  444. div.id = "libraryhelper";
  445. div.className = "libraryhelper";
  446. div.textContent = 'Library helper';
  447. // Make the DIV element draggable:
  448. dragElement(div);
  449. document.body.appendChild(div);
  450.  
  451. var style = document.createElement('style');
  452. style.innerHTML = `
  453. div.libraryhelper {
  454. color: blueviolet;
  455. border: 1px solid #d3d3d3;
  456. background-color: rgba(255, 255, 255, 0.6);
  457.  
  458. position: fixed;
  459. top: 150px;
  460. right: 0px;
  461.  
  462. width: 30vw;
  463. max-height: 50vh;
  464. padding: 10px;
  465.  
  466. overflow-x: scroll;
  467. cursor: move;
  468. z-index: 9999;
  469. }
  470. `;
  471. document.head.appendChild(style);
  472. return div;
  473. }
  474.  
  475.  
  476. function dragElement(elmnt) {
  477. var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  478. if (document.getElementById(elmnt.id + "header")) {
  479. // if present, the header is where you move the DIV from:
  480. document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
  481. } else {
  482. // otherwise, move the DIV from anywhere inside the DIV:
  483. elmnt.onmousedown = dragMouseDown;
  484. }
  485.  
  486. function dragMouseDown(e) {
  487. e = e || window.event;
  488. e.preventDefault();
  489. // get the mouse cursor position at startup:
  490. pos3 = e.clientX;
  491. pos4 = e.clientY;
  492. document.onmouseup = closeDragElement;
  493. // call a function whenever the cursor moves:
  494. document.onmousemove = elementDrag;
  495. }
  496.  
  497. function elementDrag(e) {
  498. e = e || window.event;
  499. e.preventDefault();
  500. // calculate the new cursor position:
  501. pos1 = pos3 - e.clientX;
  502. pos2 = pos4 - e.clientY;
  503. pos3 = e.clientX;
  504. pos4 = e.clientY;
  505. // set the element's new position:
  506. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  507. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  508. }
  509.  
  510. function closeDragElement() {
  511. // stop moving when mouse button is released:
  512. document.onmouseup = null;
  513. document.onmousemove = null;
  514. }
  515. }
  516.  
  517. function evaluate(xpath, doc = document.documentElement) {
  518. var evaluator = new XPathEvaluator();
  519. var result = evaluator.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null);
  520. var node = undefined;
  521. var texts = [];
  522.  
  523. while(node = result.iterateNext()) {
  524. var text;
  525.  
  526. if (node instanceof Attr) {
  527. text = node.value
  528. } else {
  529. text = node.innerText;
  530. }
  531. if(text == undefined || text == 'null') {
  532. console.error(xpath + " not found on " + document.URL);
  533. continue;
  534. }
  535. // fixing up content
  536. text = text.replace("ISBN:", "").
  537. replace("作者 :", "").
  538. replace("出版日期:", "");
  539. const subtitles = text.split(':');
  540. if(subtitles.length > 1) {
  541. texts.push(subtitles[0]);
  542. }
  543. texts.push(text);
  544. }
  545. return texts;
  546. }
  547. })();
  548.  

QingJ © 2025

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