Calibro

Looks for torrents for books in Calibre

  1. // ==UserScript==
  2. // @name Calibro
  3. // @namespace CalibroNS
  4. // @description Looks for torrents for books in Calibre
  5. // @include about:blank?calibro
  6. // @include */calibro.html
  7. // @version 1.1.4
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addStyle
  12. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
  13. // @require https://cdnjs.cloudflare.com/ajax/libs/floatthead/1.2.13/jquery.floatThead.min.js
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.serializeJSON/2.6.1/jquery.serializejson.min.js
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js
  16. // ==/UserScript==
  17.  
  18. var userAgent = "Calibro/1.1.3 (https://greasyfork.org/en/scripts/11171-calibro)";
  19.  
  20. // Case insensitive $.contains
  21. $.extend($.expr[":"], {
  22. "icontains": function(elem, i, match, array) {
  23. return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
  24. }
  25. });
  26.  
  27. var Calibro = function() {
  28.  
  29. var instance = this;
  30.  
  31. this.calibreServer = 'http://' + GM_getValue("calibreServer", "localhost:8080");
  32.  
  33. // Prevents cached response after switching to another library
  34. this.apiIds = this.calibreServer + '/ajax/search?num=' + Math.floor(Math.random() * (1000000 - 100000 + 1)) + 100000;
  35.  
  36. this.apiBooks = this.calibreServer + '/ajax/books?ids=';
  37.  
  38. this.timeout = 10000;
  39.  
  40. this.authorStoplist = [
  41. 'Unknown',
  42. 'Publish',
  43. 'Неизв',
  44. 'American',
  45. 'Press',
  46. 'Oxford'
  47. ];
  48.  
  49. this.authorlessTitles = [
  50. 'Encyclopedia',
  51. 'Dictionary'
  52. ];
  53.  
  54. this.bookFormats = [
  55. 'pdf',
  56. 'epub',
  57. 'mobi',
  58. 'azw3',
  59. 'djvu',
  60. 'chm'
  61. ];
  62.  
  63. var defaultConfig = {
  64. show_covers: true,
  65. edition_column: 'edition',
  66. sources: {
  67. BiB: false,
  68. WCD: false,
  69. bB: false,
  70. MAM: false,
  71. TGZ: false,
  72. WFL: false,
  73. BM: false,
  74. RuTracker: false,
  75. Genesis: false,
  76. AvaxHome: false
  77. }
  78. };
  79.  
  80. this.config = $.extend(true, defaultConfig, JSON.parse(GM_getValue("config", "{\"sources\":{\"Genesis\":true,\"AvaxHome\":true}}")));
  81.  
  82. this.sources = {};
  83. this.sourceIds = [];
  84. this.domains = {};
  85. this.bookIds = [];
  86.  
  87. this.notification = $('<div id="notification" class="alert alert-info"></div>');
  88.  
  89. this.run = function()
  90. {
  91. instance.sourceIds = Object.keys(instance.sources);
  92.  
  93. $(document.head).html("<meta charset=\"utf-8\"><title>Calibro</title>");
  94.  
  95. $('<link rel="stylesheet" type="text/css">')
  96. .appendTo(document.head)
  97. // Waits for Bootstrap
  98. .attr('href', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css').load(function() {
  99.  
  100. GM_addStyle("h5 { margin-top:0; } \
  101. table {margin:0 auto;} \
  102. thead { background-color:#f6f6f6; } \
  103. th { font-weight:normal; font-size: 80%; } \
  104. td {font-size:70%;width:100px !important; background-repeat:no-repeat; background-position: 95% 8px; } \
  105. td:first-child {width:150px !important; background-repeat:no-repeat; background-position: right top; } \
  106. td:first-child:hover { cursor:pointer; color:#c00; } \
  107. .seeded { background-color: #CCFFCC; } \
  108. .ajax { background-color: #FFFFE0; }");
  109.  
  110. $(document.body).html("").append(instance.notification);
  111.  
  112. $(requestIds);
  113. });
  114. };
  115.  
  116. this.registerSource = function(id, src, domain) {
  117. if (instance.config.sources[id]) {
  118. instance.sources[id] = src;
  119. if (typeof domain === 'string') {
  120. instance.domains[id] = domain;
  121. }
  122. }
  123. return instance;
  124. };
  125.  
  126. var requestIds = function()
  127. {
  128. instance.notification.text('Connecting to the Calibre content server ('+instance.calibreServer.replace('http://', '')+')...');
  129.  
  130. GM_xmlhttpRequest({
  131. method: 'GET',
  132. url: instance.apiIds,
  133. onload: requestBooks,
  134. onerror: setCustomCalibreServer,
  135. ontimeout: setCustomCalibreServer
  136. });
  137. };
  138.  
  139. var requestBooks = function(response)
  140. {
  141. instance.bookIds = JSON.parse(response.responseText).book_ids;
  142.  
  143. instance.notification.text(instance.bookIds.length + ' books found, loading metadata...');
  144.  
  145. GM_xmlhttpRequest({
  146. method: 'GET',
  147. url: instance.apiBooks + instance.bookIds.join(','),
  148. onload: renderTable
  149. });
  150. };
  151.  
  152. var renderTable = function(response)
  153. {
  154. instance.notification.text('Preparing data...');
  155.  
  156. var i, n, row, files, filedata, size, currsize, book;
  157.  
  158. var tbody = $('<tbody></tbody>');
  159.  
  160. var books = JSON.parse(response.responseText);
  161.  
  162. for (i in instance.bookIds) {
  163.  
  164. book = books[instance.bookIds[i]];
  165.  
  166. if (!book) continue;
  167.  
  168. files = [];
  169. filedata = {};
  170. for (var format in book.format_metadata) {
  171. currsize = book.format_metadata[format].size;
  172. size = parseInt(currsize) / 1024;
  173. size = size < 1024 ? size.toFixed(2) + ' KB' : (parseInt(currsize) / (1024 * 1024)).toFixed(2) + ' MB';
  174. files.push('<a href="' + instance.calibreServer + '/get/' + format + '/' + instance.bookIds[i] + '"><b>' + format.toUpperCase() + '</b> ' + size + '</a>');
  175. filedata[format.toUpperCase()] = size;
  176. }
  177.  
  178. row = $('<tr></tr>')
  179. .data('filedata', filedata)
  180. .data('book', book);
  181.  
  182. edition = (instance.config.edition_column.length > 0 && '#' + instance.config.edition_column in book.user_metadata && parseInt(book.user_metadata['#' + instance.config.edition_column]['#value#']) > 1) ? ' (' + book.user_metadata['#' + instance.config.edition_column]['#value#'] + 'ed)' : '';
  183.  
  184. row.append('<td contenteditable="true">' + book.title + edition + '</td><td>' + files.join('<br>') + '</td>');
  185.  
  186. for (n in instance.sourceIds) {
  187. if (instance.sourceIds[n] in instance.domains && "bt" + instance.domains[instance.sourceIds[n]] in book.identifiers) {
  188. row.append('<td class="seeded"></td>');
  189. } else {
  190. row.append('<td></td>');
  191. }
  192. }
  193.  
  194. row.appendTo(tbody);
  195. }
  196.  
  197. $("td:first-child", tbody).on("click", clickEvent);
  198.  
  199. var tbl = '<table class="table table-bordered table-striped main">';
  200. tbl += '<thead><tr><th><button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#settings">Settings</button></th><th>Calibre</th>';
  201. for (i in instance.sourceIds) {
  202. tbl += '<th>' + instance.sourceIds[i] + '</th>';
  203. }
  204. tbl += '</tr></thead></table>';
  205.  
  206. tbl = $(tbl).append(tbody);
  207.  
  208. instance.notification.replaceWith(tbl);
  209.  
  210. tbl.floatThead();
  211.  
  212. var source_checkboxes = '';
  213. for (i in instance.config.sources) {
  214. source_checkboxes += '<div class=\"checkbox\"><label><input type=\"checkbox\" name=\"sources['+i+']\" value=\"true\" "'+(instance.config.sources[i] ? " checked" : "")+'> '+i+'</label></div>';
  215. }
  216.  
  217. var configForm = $("<form class=\"modal\" id=\"settings\" tabindex=\"-1\">\
  218. <div class=\"modal-dialog modal-sm\">\
  219. <div class=\"modal-content\">\
  220. <div class=\"modal-header\">\
  221. <button type=\"button\" class=\"close\" data-dismiss=\"modal\"><span>&times;</span></button>\
  222. <h4 class=\"modal-title\">Settings</h4>\
  223. </div>\
  224. <div class=\"modal-body\">\
  225. <div class=\"form-group\" style=\"margin-bottom:-5px\"><label>Choose sources:</label></div>" + source_checkboxes + "\
  226. <div class=\"form-group\"><label>Edition # column (optional)</label><input class=\"form-control\" name=\"edition_column\" value=\""+instance.config.edition_column.replace(/\"/g, "&quot;")+"\"></div>\
  227. <div class=\"checkbox\"><label><input type=\"checkbox\" name=\"show_covers\" value=\"true\""+(instance.config.show_covers ? " checked" : "")+"> Show covers</label></div>\
  228. </div>\
  229. <div class=\"modal-footer\">\
  230. <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\
  231. <button type=\"button\" class=\"btn btn-primary\">Save & refresh</button>\
  232. </div>\
  233. </div>\
  234. </div>\
  235. </form>");
  236.  
  237. $("button:last", configForm).on("click", function(){
  238. var data = $.extend({show_covers:false}, configForm.serializeJSON());
  239. GM_setValue("config", JSON.stringify(data));
  240. document.location.reload();
  241. });
  242.  
  243. configForm.appendTo(document.body);
  244. };
  245.  
  246. var clickEvent = function(event)
  247. {
  248. var i;
  249. var titleCell = $(event.target);
  250. var row = titleCell.parent();
  251. var book = row.data("book");
  252.  
  253. var title = titleCell.text().trim();
  254. title = title.split(':')[0].replace(/[\ ]+/g, ' ').split(' ').length > 1 ? title.split(':')[0] : title;
  255. title = title.split('(')[0].replace(/[\ ]+/g, ' ').split(' ').length > 1 ? title.split('(')[0] : title;
  256. title = title.split(' Vol.')[0].split(' Volume')[0].replace(/^(A|An|The) /, '').replace(/[\-\–]+/g, ' ').replace(/[\ ]+/g, ' ');
  257. title = title.replace(/ \(\d+ed\)/, '')
  258. title = title.trim();
  259. book.searchTitle = title;
  260.  
  261. book.wordCount = title.replace(/\ (a|an|the|and|&|of|in|on|\+)\ /ig, ' ').replace(/[\ ]+/g, ' ').split(' ').length;
  262.  
  263. if (book.wordCount < 3) {
  264. if (!("searchAuthor" in book)) {
  265. book.searchAuthor = book.authors.length > 0 ? book.authors[0].replace(/\(ed\)/ig, '').replace(/[\ ]+/g, ' ').split(' ').pop() + ' ' : '';
  266.  
  267. for (i in instance.authorStoplist) {
  268. if (book.searchAuthor.indexOf(instance.authorStoplist[i]) > -1) {
  269. book.searchAuthor = '';
  270. break;
  271. }
  272. }
  273.  
  274. if (book.searchAuthor !== '') {
  275. for (i in instance.authorlessTitles) {
  276. if (book.title.indexOf(instance.authorlessTitles[i]) > -1) {
  277. book.searchAuthor = '';
  278. break;
  279. }
  280. }
  281. }
  282. }
  283. } else {
  284. book.searchAuthor = '';
  285. }
  286.  
  287. if (instance.config.show_covers) {
  288. titleCell.css("padding-right", "70px").css("backgroundImage", "url(" + instance.calibreServer + book.thumbnail + ")");
  289. }
  290.  
  291. $("td:gt(1)", row).each(performRequest);
  292. };
  293.  
  294. var performRequest = function () {
  295. var ajax, cell = $(this);
  296. var book = cell.parent().data("book");
  297. var pos = cell.index() - 2;
  298. var source = instance.sources[instance.sourceIds[pos]];
  299.  
  300. ajax = $.extend({
  301. method: 'GET',
  302. timeout: instance.timeout,
  303. context: this,
  304. headers: {
  305. "User-Agent": userAgent
  306. },
  307. onerror: function (r) {
  308. var label = $("<a class=\"label label-danger\">Error! Retry?</a>")
  309. .on("click", function () {
  310. cell.html("");
  311. GM_xmlhttpRequest(ajax);
  312. });
  313. cell
  314. .removeClass("ajax")
  315. .html("")
  316. .append(label, " <a class=\"label label-info\" href=\""+ajax.url+"\" target=\"_blank\">Go &rarr;</a>");
  317. },
  318. onreadystatechange:function(r){
  319. r.readyState === 4 ? cell.removeClass("ajax") : cell.addClass("ajax");
  320. },
  321. ontimeout: function (r) {
  322. var label = $("<a class=\"label label-danger\">Timeout! Retry?</a>")
  323. .on("click", function () {
  324. cell.html("");
  325. ajax.timeout += instance.timeout;
  326. GM_xmlhttpRequest(ajax);
  327. });
  328. cell
  329. .removeClass("ajax")
  330. .html("")
  331. .append(label, " <a class=\"label label-info\" href=\""+ajax.url+"\" target=\"_blank\">Go &rarr;</a>");
  332. }
  333. }, source);
  334.  
  335. if (typeof ajax.url === 'string') {
  336. ajax.url += encodeURIComponent(book.searchTitle);
  337. } else {
  338. ajax.url = ajax.url(book);
  339. }
  340.  
  341. GM_xmlhttpRequest(ajax);
  342. };
  343.  
  344. this.checkAuth = function(response) {
  345. if (response.finalUrl.indexOf("/login") > -1 || (response.finalUrl.indexOf("thegeeks.bz") > -1 && response.responseText.indexOf("<title>404 - Not Found</title>") > -1)) {
  346. $(response.context).html("<a class=\"label label-warning\" href=\"" + response.finalUrl + "\" target=\"_blank\">Login</a>");
  347. return false;
  348. }
  349. return true;
  350. };
  351.  
  352. this.prepareHTML = function(text) {
  353. return text.replace(/<img /g, '<meta ');
  354. };
  355.  
  356. var setCustomCalibreServer = function()
  357. {
  358. var form = $('<form class="form-inline" style="display:inline;margin:0"><input class="form-control input-sm" placeholder="localhost:8080" style="width:200px;"></form>');
  359.  
  360. form.on("submit", function(){
  361. var value = $('input', this).first().val().trim();
  362. if (value.length === 0) {
  363. alert('Please, set a correct value.');
  364. return false;
  365. }
  366. GM_setValue("calibreServer", value);
  367. document.location.reload();
  368. return false;
  369. });
  370.  
  371. instance.notification.append('<br>Make sure the server is running. Using different host? Set it here: http://', form);
  372. };
  373. };
  374.  
  375.  
  376. var app = new Calibro();
  377.  
  378. app.registerSource("BiB", {
  379. url: function (book) {
  380. var authors = book.searchAuthor.length > 0 ? '@authors ' + book.searchAuthor : '';
  381. var title = book.searchTitle.replace(/\!/g, '');
  382. return 'https://bibliotik.me/torrents/?cat[]=5&search=' + encodeURIComponent(authors + '@title ' + title);
  383. },
  384. onload: function (response) {
  385. if (!app.checkAuth(response)) return;
  386.  
  387. var cell = $(response.context);
  388. var html = $(app.prepareHTML(response.responseText));
  389. var filedata = cell.parent().data('filedata');
  390. var torrents = $('tr.torrent', html);
  391.  
  392. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">BiB (' + torrents.length + ')</a></h5>');
  393.  
  394. torrents.each(function () {
  395. var format = $('.torFormat', this).first().text().replace(/[\[\]]/g, '').trim();
  396. var retail = $('.torRetail', this).length ? $('.torRetail', this).first().text().trim().replace('[Retail]', '<b>[R]</b> ') : '';
  397.  
  398. $('time', this).remove();
  399. var size = $('td', this).eq(4).text().split(',');
  400. size.shift();
  401. size = size.join("").trim();
  402.  
  403. var href = $('a', this).first().attr('href');
  404. var dl = $('td', this).eq(2).find('a').first().attr('href');
  405. var dl_link = $('<a href="https://bibliotik.me' + dl + '"><b>' + format + '</b> ' + retail + size + '</a>');
  406.  
  407. if (filedata.hasOwnProperty(format) && filedata[format] == size) {
  408. dl_link.css('color', '#c00');
  409. }
  410.  
  411. $('.taglist', this).remove();
  412.  
  413. var linkTitle = $('td', this).eq(1).text().trim();
  414.  
  415. cell.append(
  416. dl_link, ' ',
  417. $('<a href="https://bibliotik.me' + href + '" target="_blank">&rarr;</a>').attr("title", linkTitle).tooltip(),
  418. '<br>'
  419. );
  420. });
  421. }
  422. }, "bibliotik");
  423.  
  424. app.registerSource("WCD", {
  425. url: function (book) {
  426. return 'https://what.cd/torrents.php?order_by=time&order_way=desc&group_results=1&filter_cat[3]=1&action=advanced&searchsubmit=1&groupname=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
  427. },
  428. onload: function (response) {
  429. if (!app.checkAuth(response)) return;
  430.  
  431. var cell = $(response.context);
  432. var html = $(app.prepareHTML(response.responseText).replace(/href=\"([a-z]+)/gi, 'href="https://what.cd/$1'));
  433. var filedata = cell.parent().data('filedata');
  434. var filekeys = Object.keys(filedata);
  435. var torrents = $('tr.torrent', html);
  436.  
  437. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">WCD (' + torrents.length + ')</a></h5>');
  438.  
  439. torrents.each(function () {
  440. var i;
  441. var link = $('a[href*="&torrentid"]', this).first();
  442. var title = link.text().trim();
  443. var href = link.attr('href');
  444. var dl = $('a[href*="action=download"]', this).first().attr('href');
  445. var size = $('td', this).eq(5).text().replace(/,/g, '').trim();
  446. var formats = [];
  447. var tags = $('.tags', this).first().text();
  448.  
  449. for (i in app.bookFormats) {
  450. if (tags.indexOf(app.bookFormats[i]) > -1) {
  451. formats.push(app.bookFormats[i].toUpperCase());
  452. break;
  453. }
  454. }
  455.  
  456. var dl_link = $('<a href="' + dl + '"><b>' + formats.join() + '</b> ' + size + '</a>');
  457.  
  458. for (i in filekeys) {
  459. if (filedata[filekeys[i]] == size) {
  460. dl_link.css('color', '#c00');
  461. break;
  462. }
  463. }
  464.  
  465. cell.append(
  466. dl_link, ' ',
  467. $('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
  468. '<br>'
  469. );
  470. });
  471. }
  472. }, "what");
  473.  
  474. app.registerSource("bB", {
  475. url: function (book) {
  476. return 'https://baconbits.org/torrents.php?action=simple&filter_cat[3]=1&searchstr=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
  477. },
  478. onload: function (response) {
  479. if (!app.checkAuth(response)) return;
  480.  
  481. var cell = $(response.context);
  482. var filedata = cell.parent().data('filedata');
  483. var html = $(app.prepareHTML(response.responseText).replace(/href="([a-z]+)/gi, 'href="https://baconbits.org/$1'));
  484. var torrents = $('tr.torrent', html);
  485.  
  486. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">bB (' + torrents.length + ')</a></h5>');
  487.  
  488. torrents.each(function () {
  489. var link = $('a', this).eq(3);
  490. var title = link.text().trim();
  491. var href = link.attr('href');
  492. var dl = $('a', this).eq(1).attr('href');
  493. var size = $('td', this).eq(4).text().replace(/,/g, '').trim();
  494. var text = $('td', this).eq(1).text().trim();
  495. var format = text.split('[') [2].split(']') [0].split(' / ') [0].trim();
  496. var retail = text.indexOf(' Retail!') > -1 ? ' <b>[R]</b> ' : '';
  497.  
  498. var dl_link = $('<a href="' + dl + '"><b>' + format + '</b> ' + retail + size + '</a>');
  499.  
  500. if (filedata.hasOwnProperty(format) && filedata[format] == size) {
  501. dl_link.css('color', '#c00');
  502. }
  503.  
  504. var td = $('td', this).eq(1);
  505. $('.tags', td).remove();
  506. $('span:eq(0)', td).remove();
  507.  
  508. cell.append(
  509. dl_link, ' ',
  510. $('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", td.text().trim()).tooltip(),
  511. '<br>'
  512. );
  513. });
  514. }
  515. }, "baconbits");
  516.  
  517. app.registerSource("MAM", {
  518. url: function (book) {
  519. var query = book.searchAuthor + '"' + book.searchTitle + '"';
  520. return 'https://www.myanonamouse.net/tor/js/loadSearch.php?tor[srchIn]=3&tor[fullTextType]=old&tor[author]=&tor[series]=&tor[narrator]=&tor[searchType]=all&tor[searchIn]=torrents&tor[hash]=&tor[sortType]=default&tor[startNumber]=0&tor[cat][]=60&tor[cat][]=71&tor[cat][]=72&tor[cat][]=90&tor[cat][]=61&tor[cat][]=73&tor[cat][]=101&tor[cat][]=62&tor[cat][]=63&tor[cat][]=107&tor[cat][]=64&tor[cat][]=74&tor[cat][]=102&tor[cat][]=76&tor[cat][]=77&tor[cat][]=65&tor[cat][]=103&tor[cat][]=115&tor[cat][]=91&tor[cat][]=66&tor[cat][]=78&tor[cat][]=138&tor[cat][]=67&tor[cat][]=79&tor[cat][]=80&tor[cat][]=92&tor[cat][]=118&tor[cat][]=94&tor[cat][]=120&tor[cat][]=95&tor[cat][]=81&tor[cat][]=82&tor[cat][]=68&tor[cat][]=69&tor[cat][]=75&tor[cat][]=96&tor[cat][]=104&tor[cat][]=109&tor[cat][]=70&tor[cat][]=112&tor[cat][]=0&tor[text]=' + encodeURIComponent(query);
  521. },
  522. onload: function (response) {
  523. if (!app.checkAuth(response)) return;
  524.  
  525. var cell = $(response.context);
  526. var row = cell.parent();
  527. var filedata = row.data('filedata');
  528. var filekeys = Object.keys(filedata);
  529. var html = $(app.prepareHTML(response.responseText).replace(/href="\//gi, 'href="https://www.myanonamouse.net/'));
  530.  
  531. var title = row.data('book').title.replace(/^(A|An|The)\ /, '').split(':')[0].split('(')[0].split('[')[0].split(' - ')[0].split(',')[0].trim().replace(/\"/g, '\"').slice(0, 20);
  532. var torrents = row.data('book').formats.length > 0 ? $('tr:not(:eq(0)):not(:contains("GB")):icontains("' + title + '")', html) : $('tr:not(:eq(0))', html);
  533.  
  534. cell.html('<h5><a href="' + response.finalUrl.replace('/js/loadSearch.php', '/browse.php') + '" target="_blank">MAM (' + torrents.length + ')</a></h5>');
  535.  
  536. torrents.each(function () {
  537. var i;
  538. $('td', this).eq(4).find('a').remove();
  539. var link = $('td', this).eq(2).find('a').first();
  540. var title = link.text().trim();
  541. var href = link.attr('href');
  542. var dl = $('td', this).eq(3).find('a').first().attr('href');
  543. var size = $('td', this).eq(4).text().trim().replace(/[\[\]]/g, '');
  544. var desc = $('td', this).eq(2).find('.torRowDesc').first();
  545. desc = desc.length > 0 ? desc.text().trim() : '';
  546.  
  547. var dl_link = $('<a href="' + dl + '"><nobr>' + size + '</nobr></a>');
  548.  
  549. for (i in filekeys) {
  550. if (parseFloat(filedata[filekeys[i]]).toFixed(1) == parseFloat(size)) {
  551. dl_link.css('color', '#c00');
  552. break;
  553. }
  554. }
  555.  
  556. cell.append(
  557. '<a href="' + href + '" target="_blank">' + title + '</a><br>' + desc + ' ',
  558. dl_link, '<br><br>'
  559. );
  560. });
  561. }
  562. }, "myanonamouse");
  563.  
  564. app.registerSource("WFL", {
  565. url: function (book) {
  566. return 'https://waffles.ch/browse.php?c86=1&c87=1&q=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
  567. },
  568. onload: function (response) {
  569. if (!app.checkAuth(response)) return;
  570.  
  571. var cell = $(response.context);
  572. var filedata = cell.parent().data('filedata');
  573. var filekeys = Object.keys(filedata);
  574. var html = $(app.prepareHTML(response.responseText).replace(/href="\//gi, 'href="https://waffles.ch/'));
  575. var torrents = $('#browsetable tr:not(:eq(0))', html);
  576.  
  577. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">WFL (' + torrents.length + ')</a></h5>');
  578.  
  579. torrents.each(function () {
  580. var i;
  581. var href = $('a', this).eq(2).attr('href');
  582. var dl = $('a', this).eq(3).attr('href');
  583. var size = $('td', this).eq(5).text().trim().toUpperCase();
  584. size = size.slice(0, -2) + ' ' + size.slice(-2);
  585. var title = $('a', this).eq(2).text().trim();
  586. var text = title.toLowerCase();
  587. var formats = [];
  588. for (i in app.bookFormats) {
  589. if (text.indexOf(app.bookFormats[i]) > -1) {
  590. formats.push(app.bookFormats[i].toUpperCase());
  591. break;
  592. }
  593. }
  594. var dl_link = $('<a href="' + dl + '"><b>' + formats.join() + '</b> ' + size + '</a>');
  595.  
  596. for (i in filekeys) {
  597. if (filedata[filekeys[i]] == size) {
  598. dl_link.css('color', '#c00');
  599. break;
  600. }
  601. }
  602.  
  603. cell.append(
  604. dl_link, ' ',
  605. $('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
  606. '<br>'
  607. );
  608. });
  609. }
  610. }, "waffles");
  611.  
  612. app.registerSource("TGZ", {
  613. url: function (book) {
  614. var author = book.searchAuthor.length > 0 ? book.searchAuthor + "and " : "";
  615. var query = author + '"' + book.searchTitle.replace(/\"/g, '') + '" and (pdf or epub or mobi or azw3) and not mp3';
  616. return 'https://thegeeks.bz/browse.php?incldead=1&nonboolean=3&titleonly=1&search=' + encodeURIComponent(query);
  617. },
  618. onload: function (response) {
  619. if (!app.checkAuth(response)) return;
  620.  
  621. var cell = $(response.context);
  622. var filedata = cell.parent().data('filedata');
  623. var filekeys = Object.keys(filedata);
  624. var html = $(app.prepareHTML(response.responseText).replace(/href="([a-z]+)/gi, 'href="https://thegeeks.bz/$1'));
  625. var torrents = $('.ttable:not(:icontains("MP3")):not(:icontains("Webrip"))', html);
  626.  
  627. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">TGZ (' + torrents.length + ')</a></h5>');
  628.  
  629. torrents.each(function () {
  630. var i;
  631. var href = $('a', this).eq(1).attr('href');
  632. var dl = $('td', this).eq(2).find('a').eq(1).attr('href');
  633. var size = $('td', this).eq(6).text().trim().toUpperCase();
  634. size = size.slice(0, -2) + ' ' + size.slice(-2);
  635. var title = $('a', this).eq(1).text().trim();
  636. var text = title.toLowerCase();
  637.  
  638. var format = '';
  639. for (i in app.bookFormats) {
  640. if (text.indexOf(app.bookFormats[i]) > -1) {
  641. format = app.bookFormats[i].toUpperCase();
  642. break;
  643. }
  644. }
  645.  
  646. var dl_link = $('<a href="' + dl + '"><b>' + format + '</b> ' + size + '</a>');
  647.  
  648. for (i in filekeys) {
  649. if (filedata[filekeys[i]] == size) {
  650. dl_link.css('color', '#c00');
  651. break;
  652. }
  653. }
  654.  
  655. cell.append(
  656. dl_link, ' ',
  657. $('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
  658. '<br>'
  659. );
  660. });
  661. }
  662. }, "thegeeks");
  663.  
  664. app.registerSource("RuTracker", {
  665. url: function (book) {
  666. var query = book.searchAuthor + book.searchTitle + ' ' + app.bookFormats.join('|');
  667. return 'http://rutracker.org/forum/tracker.php?nm=' + encodeURIComponent(query);
  668. },
  669. onload: function (response) {
  670. if (!app.checkAuth(response)) return;
  671.  
  672. var cell = $(response.context);
  673. var filedata = cell.parent().data('filedata');
  674. var filekeys = Object.keys(filedata);
  675. var html = $(app.prepareHTML(response.responseText).replace(/href="viewtopic/gi, 'href="http://rutracker.org/forum/viewtopic'));
  676. var torrents = $('#tor-tbl tbody tr.hl-tr', html);
  677.  
  678. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">TRU (' + torrents.length + ')</a></h5>');
  679.  
  680. torrents.each(function () {
  681. var i;
  682. var link = $('.t-title', this).first().find('a').first();
  683. var href = link.attr('href');
  684. var size = parseInt($('td', this).eq(5).find('u').text().trim());
  685. var i = Math.floor(Math.log(size) / Math.log(1024));
  686. size = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
  687.  
  688. for (i in filekeys) {
  689. if (filedata[filekeys[i]] == size) {
  690. size = "<span style=\"color:#c00\">"+size+"</span>";
  691. break;
  692. }
  693. }
  694.  
  695. cell.append('<a href="' + href + '" target="_blank">' + link.text().trim() + '</a> - ' + size + '<br><br>');
  696. });
  697. }
  698. }, "rutracker");
  699.  
  700. app.registerSource("BM", {
  701. url: function (book) {
  702. return 'http://www.bitme.org/browse.php?cat=6&incldead=1&search=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
  703. },
  704. onload: function (response) {
  705. if (!app.checkAuth(response)) return;
  706.  
  707. var cell = $(response.context);
  708. var row = cell.parent();
  709. var filedata = row.data('filedata');
  710. var filekeys = Object.keys(filedata);
  711. var html = $(app.prepareHTML(response.responseText).replace(/href="/gi, 'href="http://www.bitme.org/'));
  712.  
  713. var table = $("td.latest", html).first().closest("table");
  714.  
  715. var title = row.data('book').title.replace(/^(A|An|The)\ /, '').split(':')[0].split('(')[0].split('[')[0].split(' - ')[0].split(',')[0].trim().replace(/\"/g, '\"').slice(0, 20);
  716. var torrents = row.data('book').formats.length > 0 ? $('tr:not(:eq(0)):not(:contains("GB")):icontains("' + title + '")', table) : $('tr:not(:eq(0))', table);
  717.  
  718. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">BM (' + torrents.length + ')</a></h5>');
  719.  
  720. torrents.each(function () {
  721. var i;
  722. var href = $('a', this).eq(1).attr('href');
  723. var dl = $('a', this).eq(3).attr('href');
  724. var size = $('td', this).eq(6).text().trim().toUpperCase();
  725. size = size.slice(0, -2) + ' ' + size.slice(-2);
  726. var title = $('a', this).eq(1).text().trim();
  727.  
  728. var dl_link = $('<a href="' + dl + '">' + size + '</a>');
  729.  
  730. for (i in filekeys) {
  731. if (filedata[filekeys[i]] == size) {
  732. dl_link.css('color', '#c00');
  733. break;
  734. }
  735. }
  736.  
  737. cell.append(
  738. '<a href="' + href + '" target="_blank">' + title + '</a><br>',
  739. dl_link, "<br><br>"
  740. );
  741. });
  742. }
  743. }, "bitme");
  744.  
  745. app.registerSource("Genesis", {
  746. url: function (book) {
  747. return 'http://gen.lib.rus.ec/search.php?open=0&view=simple&column=def&req=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
  748. },
  749. onload: function (response) {
  750. var cell = $(response.context);
  751.  
  752. var html = app.prepareHTML(response.responseText).split('<table width=100% cellspacing=1')[1];
  753. html = '<table width=100% cellspacing=1' + html;
  754. html = html.split('</table')[0];
  755. html += '</table>';
  756. html = $(html);
  757.  
  758. var items = $('tr:not(:eq(0))', html);
  759.  
  760. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">GEN (' + items.length + ')</a></h5>');
  761.  
  762. items.each(function () {
  763. var row = $(this);
  764. var link = row.find("a").eq(1);
  765. link.find("font:last").remove();
  766. var title = link.text().trim();
  767. var dl = '<div>';
  768. dl += row.find('td').eq(8).text() + ' ' + row.find('td').eq(7).text() + ' ';
  769. dl += row.find('td').eq(9).html() + ' ';
  770. dl += row.find('td').eq(10).html() + ' ';
  771. dl += row.find('td').eq(11).html() + ' ';
  772. dl += row.find('td').eq(12).html() + ' ';
  773. dl += '</div>';
  774. cell.append($(dl).attr("title", title).tooltip({placement:"left"}));
  775. });
  776. }
  777. });
  778.  
  779. app.registerSource("AvaxHome", {
  780. url: 'http://avxsearch.se/?c=5&exact=1&q=',
  781. onload: function (response) {
  782. var cell = $(response.context);
  783. var html = $(app.prepareHTML(response.responseText));
  784. var items = $('.article:not(:icontains("MP3"))', html);
  785.  
  786. cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">AVX (' + items.length + ')</a></h5>');
  787.  
  788. items.each(function () {
  789. cell.append($('a.title-link', this).first().attr("target", "_blank"));
  790.  
  791. var text = $('div.center', this).first().contents().filter(function () {
  792. return this.nodeType === 3;
  793. });
  794. text = text.text().trim();
  795. if (text.length > 0) {
  796. text = text.split(' | ');
  797. if (text.length > 4) {
  798. text.shift();
  799. text.shift();
  800. }
  801. text = text.join(' | ');
  802. cell.append('<br>', text);
  803. }
  804.  
  805. cell.append('<br><br>');
  806. });
  807. }
  808. });
  809.  
  810. app.run();