Library Helper

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

  1. // ==UserScript==
  2. // @name Library Helper
  3. // @namespace https://github.com/chihchun
  4. // @version 1.17
  5. // @description A userscript that display links between different libraries and book stores.
  6. // @author Rex Tsai <rex.cc.tsai@gmail.com>
  7. // @match https://book.tpml.edu.tw/bookDetail/*
  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. // @match https://www.eslite.com/product/*
  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. eslite.com:
  108. matches:
  109. - "https://www.eslite.com/product/*"
  110. type: 'XPATH'
  111. metadata:
  112. title: "//div[@class='product-information']//h1"
  113. authors: "//a[contains(@href,'/search?author')]"
  114. delaytime: 2000
  115.  
  116. goodreads.com:
  117. matches:
  118. - "https://www.goodreads.com/book/show/*"
  119. type: 'XPATH'
  120. metadata:
  121. title: "//meta[@property='og:title']/@content"
  122. authors: "//a[@class='authorName']/span[@itemprop='name']"
  123. isbn: "//meta[@property='books:isbn']/@content"
  124. rating: "//span[@itemprop='ratingValue']"
  125.  
  126. kobo.com:
  127. matches:
  128. - "https://www.kobo.com/*"
  129. type: 'JSON-LD'
  130. metadata:
  131. title: '//span[@class="title product-field"]'
  132. authors: '//a[@class="contributor-name"]'
  133. origtitle: "//span[contains(@class, 'subtitle')]"
  134.  
  135. play.google.com:
  136. matches:
  137. - "https://play.google.com/store/books/details/*"
  138. - "https://play.google.com/store/books/details?id=*"
  139. type: 'JSON-LD'
  140. metadata:
  141.  
  142. readmoo.com:
  143. matches:
  144. - "https://share.readmoo.com/book/*"
  145. - "https://readmoo.com/book/*"
  146. type: 'XPATH'
  147. metadata:
  148. title: "//h2"
  149. isbn: "//span[@itemprop='ISBN']"
  150. authors: "//span[@itemprop='name']/a"
  151.  
  152. taaze.tw:
  153. matches:
  154. - "https://www.taaze.tw/goods/*"
  155. type: 'XPATH'
  156. metadata:
  157. title: "//div[contains(@class, 'mBody')]//h1"
  158. origtitle: "//div[contains(@class, 'mBody')]//h2"
  159. isbn: "//meta[@property='books:isbn']/@content"
  160. authors: "//div[@class='authorBrand']//a[contains(@href,'rwd_searchResult.html?keyType%5B%5D=2')]"
  161.  
  162. taaze.tw/used:
  163. matches:
  164. - "https://www.taaze.tw/usedList.html*"
  165. type: 'XPATH'
  166. metadata:
  167. origtitle: "//div[contains(@class, 'hide')]//div[@class='title-next']"
  168. authors: "//a[contains(@href,'rwd_searchResult.html?keyType%5B%5D=2')]"
  169.  
  170. tpml.edu.tw:
  171. matches:
  172. - "https://book.tpml.edu.tw/bookDetail/*"
  173. type: 'XPATH'
  174. metadata:
  175. title: "//div[@class='bookdata']/h2"
  176. authors: "//a[contains(@href,'searchField=PN')]"
  177. delaytime: 1200
  178. fixtitle: True
  179.  
  180. webpac.tphcc.gov.tw:
  181. matches:
  182. - "https://webpac.tphcc.gov.tw/webpac/content.cfm*"
  183. type: 'XPATH'
  184. metadata:
  185. title: "//h2"
  186. authors: "//div[@class='detail simple']/p[1]"
  187. isbn: "//div[@class='detail simple']/p[4]"
  188.  
  189. `;
  190.  
  191. var search_yaml = `
  192. 博客來:
  193. url: "https://search.books.com.tw/search/query/key/"
  194. languages:
  195. - "en"
  196. - "en-US"
  197. - "zh"
  198. - "zh-TW"
  199. - "zh-HK"
  200. - "zh-CN"
  201.  
  202. 誠品線上:
  203. url: "https://www.eslite.com/Search?keyword="
  204. languages:
  205. - "en"
  206. - "en-US"
  207. - "zh"
  208. - "zh-TW"
  209. - "zh-HK"
  210. - "zh-CN"
  211.  
  212. Kobo:
  213. url: "https://www.kobo.com/tw/zh/search?query="
  214. languages:
  215. - "en"
  216. - "en-US"
  217. - "zh"
  218. - "zh-TW"
  219. - "zh-HK"
  220. - "zh-CN"
  221. - "fr"
  222. - "fr-ca"
  223. - "fr-fr"
  224.  
  225. GooglePlay:
  226. url: "https://play.google.com/store/search?c=books&q="
  227. languages:
  228. - "en"
  229. - "en-US"
  230. - "zh"
  231. - "zh-TW"
  232. - "zh-HK"
  233. - "zh-CN"
  234. - "fr"
  235. - "fr-ca"
  236. - "fr-fr"
  237.  
  238. AmazonCN:
  239. url: "https://www.amazon.cn/s?rh=n%3A116169071&k="
  240. languages:
  241. - "en"
  242. - "en-US"
  243. - "zh"
  244. - "zh-TW"
  245. - "zh-HK"
  246. - "zh-CN"
  247.  
  248. 豆瓣:
  249. url: "https://search.douban.com/book/subject_search?search_text="
  250. languages:
  251. - "zh"
  252. - "zh-TW"
  253. - "zh-HK"
  254. - "zh-CN"
  255.  
  256. Goodreads:
  257. url: "https://www.goodreads.com/search?q="
  258. languages:
  259. - "en"
  260. - "en-US"
  261. - "zh"
  262. - "zh-TW"
  263. - "zh-HK"
  264. - "zh-CN"
  265. - "fr"
  266. - "fr-ca"
  267. - "fr-fr"
  268.  
  269. Google:
  270. url: "https://www.google.com/search?tbm=bks&q="
  271. languages:
  272. - "en"
  273. - "en-US"
  274. - "zh"
  275. - "zh-TW"
  276. - "zh-HK"
  277. - "zh-CN"
  278. - "fr"
  279. - "fr-ca"
  280. - "fr-fr"
  281.  
  282. 北市圖書館:
  283. url: "https://book.tpml.edu.tw/search?searchField=FullText&searchInput="
  284. languages:
  285. - "en"
  286. - "en-US"
  287. - "zh"
  288. - "zh-TW"
  289. - "zh-HK"
  290. - "zh-CN"
  291.  
  292. 北市圖書館Hyread:
  293. url: "https://tpml.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  294. languages:
  295. - "zh"
  296. - "zh-TW"
  297. - "zh-HK"
  298.  
  299. 北市圖書館UDN讀書館:
  300. url: "https://reading.udn.com/udnlib/tpml/booksearch?sort=all&opt=all&kw="
  301. languages:
  302. - "zh"
  303. - "zh-TW"
  304. - "zh-HK"
  305.  
  306. 北區資源中心UDN讀書館:
  307. url: "https://reading.udn.com/udnlib/publiclib/booksearch?sort=all&opt=all&kw="
  308. languages:
  309. - "zh"
  310. - "zh-TW"
  311. - "zh-HK"
  312.  
  313. 台灣雲端書庫:
  314. url: "https://www.ebookservice.tw/#search/"
  315. languages:
  316. - "zh"
  317. - "zh-TW"
  318. - "zh-HK"
  319.  
  320. 新北市圖書館:
  321. url: "https://webpac.tphcc.gov.tw/webpac/search.cfm?m=ss&t0=k&c0=and&k0="
  322. languages:
  323. - "en"
  324. - "en-US"
  325. - "zh"
  326. - "zh-TW"
  327. - "zh-HK"
  328. - "zh-CN"
  329.  
  330. 新北市圖書館Hyread:
  331. url: "https://tphcc.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  332. languages:
  333. - "zh"
  334. - "zh-TW"
  335. - "zh-HK"
  336.  
  337. 新北市圖書館UDN讀書館:
  338. url: "https://reading.udn.com/udnlib/tpc/booksearch?sort=all&opt=all&kw="
  339. languages:
  340. - "zh"
  341. - "zh-TW"
  342. - "zh-HK"
  343.  
  344. 國立臺灣圖書館Hyread:
  345. url: "https://ntledu.ebook.hyread.com.tw/searchList.jsp?search_field=FullText&search_input="
  346. languages:
  347. - "zh"
  348. - "zh-TW"
  349. - "zh-HK"
  350.  
  351. 國立臺灣圖書館UDN讀書館:
  352. url: "https://reading.udn.com/udnlib/ncl/booksearch?sort=all&opt=all&kw="
  353. languages:
  354. - "zh"
  355. - "zh-TW"
  356. - "zh-HK"
  357.  
  358. 讀冊:
  359. url: "https://www.taaze.tw/rwd_searchResult.html?keyword%5B%5D="
  360. languages:
  361. - "zh"
  362. - "zh-TW"
  363. - "zh-HK"
  364. - "zh-CN"
  365.  
  366. Readmoo:
  367. url: "https://share.readmoo.com/search/keyword?q="
  368. languages:
  369. - "zh"
  370. - "zh-TW"
  371. - "zh-HK"
  372. - "zh-CN"
  373.  
  374. Amazon:
  375. url: "https://www.amazon.com/s?i=digital-text&k="
  376. languages:
  377. - "en"
  378. - "en-US"
  379. - "zh"
  380. - "zh-TW"
  381. - "zh-HK"
  382. - "zh-CN"
  383. - "fr"
  384. - "fr-ca"
  385. - "fr-fr"
  386.  
  387. Babelio:
  388. url: "https://www.babelio.com/resrecherche.php?Recherche="
  389. languages:
  390. - "fr"
  391. - "fr-ca"
  392. - "fr-fr"
  393.  
  394. Bobigny:
  395. url: "http://bibliotheque.ville-bobigny.fr/recherche-catalogue/recherche-simple/simple/Mots%20Notice/0/"
  396. languages:
  397. - "fr"
  398. - "fr-ca"
  399. - "fr-fr"
  400. `;
  401.  
  402. var keywords = ['title', 'authors', 'origtitle', 'isbn', 'asin'];
  403.  
  404.  
  405. parse_metadata();
  406.  
  407. function parse_metadata() {
  408. var rules = jsyaml.load(metadata_yaml);
  409. var data = {};
  410.  
  411. // parse the ld+json
  412. var jsons = evaluate('//script[@type="application/ld+json"]');
  413. if(jsons.length > 0) {
  414. jsons.forEach(function(json) {
  415. try {
  416. var ld = JSON.parse(json);
  417. // console.debug(ld);
  418. if(ld['@type'] == "Book") {
  419. data['title'] = [ld['name']];
  420.  
  421. if(ld['isbn'] != undefined) {
  422. data['isbn']= [ld['isbn']];
  423. }
  424.  
  425. if(ld['workExample'] != undefined && ld['workExample']['isbn'] != undefined ) {
  426. data['isbn']= [ld['workExample']['isbn']];
  427. }
  428.  
  429. data['authors'] = [];
  430. if(ld['author'] != undefined) {
  431. if(Array.isArray(ld['author'])) {
  432. ld['author'].forEach(function (author) {
  433. data['authors'].push(author['name']);
  434. })
  435. } else {
  436. data['authors'].push(ld['author']['name']);
  437. }
  438. }
  439. }
  440. } catch(err) {
  441. console.error(err, json)
  442. }
  443. })
  444. }
  445.  
  446. for (var domain in rules) {
  447. rules[domain]['matches'].forEach(function (match) {
  448. if(document.URL.match(match)) {
  449. var metadata = rules[domain]['metadata'];
  450.  
  451. // wait for page is loaded
  452. let delaytime = 0;
  453. if('delaytime' in rules[domain]) {
  454. delaytime = rules[domain]['delaytime'];
  455. }
  456. let fixtitle = 0;
  457. if('fixtitle' in rules[domain]) {
  458. fixtitle = true;
  459. }
  460.  
  461. // wait for page content to be loaded
  462. setTimeout(function() {
  463. // collect metadata
  464. for (var key in metadata) {
  465. data[key] = evaluate(metadata[key]);
  466. }
  467.  
  468. // updates links to other websites
  469. if(Object.keys(data).length > 0) {
  470. console.debug(data);
  471. var dialog = inject();
  472. var urlsforsearch = jsyaml.load(search_yaml);
  473.  
  474. for (var service in urlsforsearch) {
  475. if(!isPreferLang(urlsforsearch[service]['languages'])) {
  476. continue;
  477. }
  478.  
  479. var url = urlsforsearch[service]['url'];
  480. var html = `<div>${service}: `;
  481. keywords.forEach(function(key) {
  482. if(data[key] != undefined) {
  483. data[key].forEach(function(val) {
  484. var href = url + encodeURI(val);
  485. html += `<a href="${href}" target="_blank">${val}</a> `;
  486. })
  487. }
  488. })
  489. html += "</div>";
  490. dialog.insertAdjacentHTML('beforeend', html)
  491. }
  492. }
  493.  
  494. // fix title for soem sites.
  495. if(fixtitle) {
  496. document.title = data["title"];
  497. }
  498. }, delaytime);
  499. }
  500. });
  501. }
  502. }
  503.  
  504. function isPreferLang(offers) {
  505. var languages = window.navigator.userLanguage || window.navigator.languages || [window.navigator.language];
  506. var ret = false;
  507. languages.forEach(function(lang) {
  508. if(offers.includes(lang)) {
  509. ret = true;
  510. }
  511. })
  512. return ret;
  513. }
  514.  
  515. function inject () {
  516. var div = document.createElement('div');
  517. div.id = "libraryhelper";
  518. div.className = "libraryhelper";
  519. div.textContent = 'Library helper';
  520. // Make the DIV element draggable:
  521. dragElement(div);
  522. document.body.appendChild(div);
  523.  
  524. var style = document.createElement('style');
  525. style.innerHTML = `
  526. div.libraryhelper {
  527. color: blueviolet;
  528. border: 1px solid #d3d3d3;
  529. background-color: rgba(255, 255, 255, 0.6);
  530.  
  531. position: fixed;
  532. top: 150px;
  533. right: 0px;
  534.  
  535. width: 30vw;
  536. max-height: 50vh;
  537. padding: 10px;
  538.  
  539. overflow-x: scroll;
  540. cursor: move;
  541. z-index: 9999;
  542. }
  543. `;
  544. document.head.appendChild(style);
  545. return div;
  546. }
  547.  
  548.  
  549. function dragElement(elmnt) {
  550. var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  551. if (document.getElementById(elmnt.id + "header")) {
  552. // if present, the header is where you move the DIV from:
  553. document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
  554. } else {
  555. // otherwise, move the DIV from anywhere inside the DIV:
  556. elmnt.onmousedown = dragMouseDown;
  557. }
  558.  
  559. function dragMouseDown(e) {
  560. e = e || window.event;
  561. e.preventDefault();
  562. // get the mouse cursor position at startup:
  563. pos3 = e.clientX;
  564. pos4 = e.clientY;
  565. document.onmouseup = closeDragElement;
  566. // call a function whenever the cursor moves:
  567. document.onmousemove = elementDrag;
  568. }
  569.  
  570. function elementDrag(e) {
  571. e = e || window.event;
  572. e.preventDefault();
  573. // calculate the new cursor position:
  574. pos1 = pos3 - e.clientX;
  575. pos2 = pos4 - e.clientY;
  576. pos3 = e.clientX;
  577. pos4 = e.clientY;
  578. // set the element's new position:
  579. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  580. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  581. }
  582.  
  583. function closeDragElement() {
  584. // stop moving when mouse button is released:
  585. document.onmouseup = null;
  586. document.onmousemove = null;
  587. }
  588. }
  589.  
  590. function evaluate(xpath, doc = document.documentElement) {
  591. var evaluator = new XPathEvaluator();
  592. var result = evaluator.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null);
  593. var node = undefined;
  594. var texts = [];
  595.  
  596. while(node = result.iterateNext()) {
  597. var text;
  598.  
  599. if (node instanceof Attr) {
  600. text = node.value
  601. } else {
  602. text = node.innerText;
  603. }
  604. if(text == undefined || text == 'null') {
  605. console.error(xpath + " not found on " + document.URL);
  606. continue;
  607. }
  608. // fixing up content
  609. text = text.replace("ISBN:", "").
  610. replace("作者 :", "").
  611. replace("出版日期:", "").
  612. replace(" (電子書)", "");
  613. const subtitles = text.split(':');
  614. if(subtitles.length > 1) {
  615. texts.push(subtitles[0]);
  616. }
  617. texts.push(text);
  618. }
  619. return texts;
  620. }
  621. })();

QingJ © 2025

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