Library Helper

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

当前为 2020-07-11 提交的版本,查看 最新版本

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

QingJ © 2025

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