decembre

Library Helper v.1.17 - TEST ADD use of CSS selector (PB) + Bobigny - PB Babelio

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