Calibro

Looks for torrents for books in Calibre

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name		Calibro
// @namespace   CalibroNS
// @description Looks for torrents for books in Calibre
// @include	 about:blank?calibro
// @include	 */calibro.html
// @version	 1.1.3
// @grant	 GM_xmlhttpRequest
// @grant	 GM_setValue
// @grant	 GM_getValue
// @grant    GM_addStyle
// @require	 https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @require	 https://cdnjs.cloudflare.com/ajax/libs/floatthead/1.2.13/jquery.floatThead.min.js
// @require	 https://cdnjs.cloudflare.com/ajax/libs/jquery.serializeJSON/2.6.1/jquery.serializejson.min.js
// @require	 https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js
// ==/UserScript==

var userAgent = "Calibro/1.1.3 (https://greasyfork.org/en/scripts/11171-calibro)";

// Case insensitive $.contains
$.extend($.expr[":"], {
	"icontains": function(elem, i, match, array) {
		return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0;
	}
});

var Calibro = function() {

	var instance = this;

	this.calibreServer = 'http://' + GM_getValue("calibreServer", "localhost:8080");

	// Prevents cached response after switching to another library
	this.apiIds = this.calibreServer + '/ajax/search?num=' + Math.floor(Math.random() * (1000000 - 100000 + 1)) + 100000;

	this.apiBooks = this.calibreServer + '/ajax/books?ids=';

	this.timeout = 10000;

	this.authorStoplist = [
		'Unknown',
		'Publish',
		'Неизв',
		'American',
		'Press',
		'Oxford'
	];

	this.authorlessTitles = [
		'Encyclopedia',
		'Dictionary'
	];

	this.bookFormats = [
		'pdf',
		'epub',
		'mobi',
		'azw3',
		'djvu',
		'chm'
	];

	var defaultConfig = {
		show_covers: true,
		edition_column: 'edition',
		sources: {
			BiB: false,
			WCD:  false,
			bB: false,
			MAM: false,
			TGZ: false,
			WFL: false,
			BM: false,
			RuTracker: false,
			Genesis: false,
			AvaxHome: false
		}
	};

	this.config = $.extend(true, defaultConfig, JSON.parse(GM_getValue("config", "{\"sources\":{\"Genesis\":true,\"AvaxHome\":true}}")));

	this.sources = {};
	this.sourceIds = [];
	this.domains = {};
	this.bookIds = [];

	this.notification = $('<div id="notification" class="alert alert-info"></div>');

	this.run = function()
	{
		instance.sourceIds = Object.keys(instance.sources);

		$(document.head).html("<meta charset=\"utf-8\"><title>Calibro</title>");

		$('<link rel="stylesheet" type="text/css">')
			.appendTo(document.head)
			// Waits for Bootstrap
			.attr('href', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css').load(function() {

				GM_addStyle("h5 { margin-top:0; } \
table {margin:0 auto;} \
thead { background-color:#f6f6f6; } \
th { font-weight:normal; font-size: 80%; } \
td {font-size:70%;width:100px !important; background-repeat:no-repeat; background-position: 95% 8px; } \
td:first-child {width:150px !important; background-repeat:no-repeat; background-position: right top; } \
td:first-child:hover { cursor:pointer; color:#c00; } \
.seeded { background-color: #CCFFCC; } \
.ajax { background-color: #FFFFE0; }");

				$(document.body).html("").append(instance.notification);

				$(requestIds);
			});
	};

	this.registerSource = function(id, src, domain) {
		if (instance.config.sources[id]) {
			instance.sources[id] = src;
			if (typeof domain === 'string') {
				instance.domains[id] = domain;
			}
		}
		return instance;
	};

	var requestIds = function()
	{
		instance.notification.text('Connecting to the Calibre content server ('+instance.calibreServer.replace('http://', '')+')...');

		GM_xmlhttpRequest({
			method: 'GET',
			url: instance.apiIds,
			onload: requestBooks,
			onerror: setCustomCalibreServer,
			ontimeout: setCustomCalibreServer
		});
	};

	var requestBooks = function(response)
	{
		instance.bookIds = JSON.parse(response.responseText).book_ids;

		instance.notification.text(instance.bookIds.length + ' books found, loading metadata...');

		GM_xmlhttpRequest({
			method: 'GET',
			url: instance.apiBooks + instance.bookIds.join(','),
			onload: renderTable
		});
	};

	var renderTable = function(response)
	{
		instance.notification.text('Preparing data...');

		var i, n, row, files, filedata, size, currsize, book;

		var tbody = $('<tbody></tbody>');

		var books = JSON.parse(response.responseText);

		for (i in instance.bookIds) {

			book = books[instance.bookIds[i]];

            if (!book) continue;

			files = [];
			filedata = {};
			for (var format in book.format_metadata) {
				currsize = book.format_metadata[format].size;
				size = parseInt(currsize) / 1024;
				size = size < 1024 ? size.toFixed(2) + ' KB' : (parseInt(currsize) / (1024 * 1024)).toFixed(2) + ' MB';
				files.push('<a href="' + instance.calibreServer + '/get/' + format + '/' + instance.bookIds[i] + '"><b>' + format.toUpperCase() + '</b> ' + size + '</a>');
				filedata[format.toUpperCase()] = size;
			}

			row = $('<tr></tr>')
				.data('filedata', filedata)
				.data('book', book);

			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)' : '';

			row.append('<td contenteditable="true">' + book.title + edition + '</td><td>' + files.join('<br>') + '</td>');

			for (n in instance.sourceIds) {
				if (instance.sourceIds[n] in instance.domains && "bt" + instance.domains[instance.sourceIds[n]] in book.identifiers) {
					row.append('<td class="seeded"></td>');
				} else {
					row.append('<td></td>');
				}
			}

			row.appendTo(tbody);
		}

		$("td:first-child", tbody).on("click", clickEvent);

		var tbl = '<table class="table table-bordered table-striped main">';
		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>';
		for (i in instance.sourceIds) {
			tbl += '<th>' + instance.sourceIds[i] + '</th>';
		}
		tbl += '</tr></thead></table>';

		tbl = $(tbl).append(tbody);

		instance.notification.replaceWith(tbl);

		tbl.floatThead();

		var source_checkboxes = '';
		for (i in instance.config.sources) {
			source_checkboxes += '<div class=\"checkbox\"><label><input type=\"checkbox\" name=\"sources['+i+']\" value=\"true\" "'+(instance.config.sources[i] ? " checked" : "")+'> '+i+'</label></div>';
		}

		var configForm = $("<form class=\"modal\" id=\"settings\" tabindex=\"-1\">\
<div class=\"modal-dialog modal-sm\">\
<div class=\"modal-content\">\
<div class=\"modal-header\">\
<button type=\"button\" class=\"close\" data-dismiss=\"modal\"><span>&times;</span></button>\
<h4 class=\"modal-title\">Settings</h4>\
</div>\
<div class=\"modal-body\">\
<div class=\"form-group\" style=\"margin-bottom:-5px\"><label>Choose sources:</label></div>" + source_checkboxes + "\
<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>\
<div class=\"checkbox\"><label><input type=\"checkbox\" name=\"show_covers\" value=\"true\""+(instance.config.show_covers ? " checked" : "")+"> Show covers</label></div>\
</div>\
<div class=\"modal-footer\">\
<button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\
<button type=\"button\" class=\"btn btn-primary\">Save & refresh</button>\
</div>\
</div>\
</div>\
</form>");

		$("button:last", configForm).on("click", function(){
			var data = $.extend({show_covers:false}, configForm.serializeJSON());
			GM_setValue("config", JSON.stringify(data));
			document.location.reload();
		});

		configForm.appendTo(document.body);
	};

	var clickEvent = function(event)
	{
		var i;
		var titleCell = $(event.target);
		var row = titleCell.parent();
		var book = row.data("book");

		var title = titleCell.text().trim();
		title = title.split(':')[0].replace(/[\ ]+/g, ' ').split(' ').length > 1 ? title.split(':')[0] : title;
		title = title.split('(')[0].replace(/[\ ]+/g, ' ').split(' ').length > 1 ? title.split('(')[0] : title;
		title = title.split(' Vol.')[0].split(' Volume')[0].replace(/^(A|An|The) /, '').replace(/[\-\–]+/g, ' ').replace(/[\ ]+/g, ' ');
		title = title.replace(/ \(\d+ed\)/, '')
		title = title.trim();
		book.searchTitle = title;

		book.wordCount = title.replace(/\ (a|an|the|and|&|of|in|on|\+)\ /ig, ' ').replace(/[\ ]+/g, ' ').split(' ').length;

		if (book.wordCount < 3) {
			if (!("searchAuthor" in book)) {
				book.searchAuthor = book.authors.length > 0 ? book.authors[0].replace(/\(ed\)/ig, '').replace(/[\ ]+/g, ' ').split(' ').pop() + ' ' : '';

				for (i in instance.authorStoplist) {
					if (book.searchAuthor.indexOf(instance.authorStoplist[i]) > -1) {
						book.searchAuthor = '';
						break;
					}
				}

				if (book.searchAuthor !== '') {
					for (i in instance.authorlessTitles) {
						if (book.title.indexOf(instance.authorlessTitles[i]) > -1) {
							book.searchAuthor = '';
							break;
						}
					}
				}
			}
		} else {
			book.searchAuthor = '';
		}

		if (instance.config.show_covers) {
			titleCell.css("padding-right", "70px").css("backgroundImage", "url(" + instance.calibreServer + book.thumbnail + ")");
		}

		$("td:gt(1)", row).each(performRequest);
	};

	var performRequest = function () {
		var ajax, cell = $(this);
		var book = cell.parent().data("book");
		var pos = cell.index() - 2;
		var source = instance.sources[instance.sourceIds[pos]];

		ajax = $.extend({
			method: 'GET',
			timeout: instance.timeout,
			context: this,
            headers: {
                "User-Agent": userAgent
            },
			onerror: function (r) {
				var label = $("<a class=\"label label-danger\">Error! Retry?</a>")
					.on("click", function () {
						cell.html("");
						GM_xmlhttpRequest(ajax);
					});
				cell
					.removeClass("ajax")
					.html("")
					.append(label, " <a class=\"label label-info\" href=\""+ajax.url+"\" target=\"_blank\">Go &rarr;</a>");
			},
			onreadystatechange:function(r){
				r.readyState === 4 ? cell.removeClass("ajax") : cell.addClass("ajax");
			},
			ontimeout: function (r) {
				var label = $("<a class=\"label label-danger\">Timeout! Retry?</a>")
					.on("click", function () {
						cell.html("");
						ajax.timeout += instance.timeout;
						GM_xmlhttpRequest(ajax);
					});
				cell
					.removeClass("ajax")
					.html("")
					.append(label, " <a class=\"label label-info\" href=\""+ajax.url+"\" target=\"_blank\">Go &rarr;</a>");
			}
		}, source);

		if (typeof ajax.url === 'string') {
			ajax.url += encodeURIComponent(book.searchTitle);
		} else {
			ajax.url = ajax.url(book);
		}

		GM_xmlhttpRequest(ajax);
	};

	this.checkAuth = function(response) {
		if (response.finalUrl.indexOf("/login") > -1 || (response.finalUrl.indexOf("thegeeks.bz") > -1 && response.responseText.indexOf("<title>404 - Not Found</title>") > -1)) {
			$(response.context).html("<a class=\"label label-warning\" href=\"" + response.finalUrl + "\" target=\"_blank\">Login</a>");
			return false;
		}
		return true;
	};

	this.prepareHTML = function(text) {
		return text.replace(/<img /g, '<meta ');
	};

	var setCustomCalibreServer = function()
	{
		var form = $('<form class="form-inline" style="display:inline;margin:0"><input class="form-control input-sm" placeholder="localhost:8080" style="width:200px;"></form>');

		form.on("submit", function(){
			var value = $('input', this).first().val().trim();
			if (value.length === 0) {
				alert('Please, set a correct value.');
				return false;
			}
			GM_setValue("calibreServer", value);
			document.location.reload();
			return false;
		});

		instance.notification.append('<br>Make sure the server is running. Using different host? Set it here: http://', form);
	};
};


var app = new Calibro();

app.registerSource("BiB", {
	url: function (book) {
		var authors = book.searchAuthor.length > 0 ? '@authors ' + book.searchAuthor : '';
		var title = book.searchTitle.replace(/\!/g, '');
		return 'https://bibliotik.org/torrents/?cat[]=5&search=' + encodeURIComponent(authors + '@title ' + title);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var html = $(app.prepareHTML(response.responseText));
		var filedata = cell.parent().data('filedata');
		var torrents = $('tr.torrent', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">BiB (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var format = $('.torFormat', this).first().text().replace(/[\[\]]/g, '').trim();
			var retail = $('.torRetail', this).length ? $('.torRetail', this).first().text().trim().replace('[Retail]', '<b>[R]</b> ') : '';

			$('time', this).remove();
			var size = $('td', this).eq(4).text().split(',');
			size.shift();
			size = size.join("").trim();

			var href = $('a', this).first().attr('href');
			var dl = $('td', this).eq(2).find('a').first().attr('href');
			var dl_link = $('<a href="' + dl + '"><b>' + format + '</b> ' + retail + size + '</a>');

			if (filedata.hasOwnProperty(format) && filedata[format] == size) {
				dl_link.css('color', '#c00');
			}

			$('.taglist', this).remove();

			var linkTitle = $('td', this).eq(1).text().trim();

			cell.append(
				dl_link, ' ',
				$('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", linkTitle).tooltip(),
				'<br>'
			);
		});
	}
}, "bibliotik");

app.registerSource("WCD", {
	url: function (book) {
		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);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var html = $(app.prepareHTML(response.responseText).replace(/href=\"([a-z]+)/gi, 'href="https://what.cd/$1'));
		var filedata = cell.parent().data('filedata');
		var filekeys = Object.keys(filedata);
		var torrents = $('tr.torrent', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">WCD (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			var link = $('a[href*="&torrentid"]', this).first();
			var title = link.text().trim();
			var href = link.attr('href');
			var dl = $('a[href*="action=download"]', this).first().attr('href');
			var size = $('td', this).eq(5).text().replace(/,/g, '').trim();
			var formats = [];
			var tags = $('.tags', this).first().text();

			for (i in app.bookFormats) {
				if (tags.indexOf(app.bookFormats[i]) > -1) {
					formats.push(app.bookFormats[i].toUpperCase());
					break;
				}
			}

			var dl_link = $('<a href="' + dl + '"><b>' + formats.join() + '</b> ' + size + '</a>');

			for (i in filekeys) {
				if (filedata[filekeys[i]] == size) {
					dl_link.css('color', '#c00');
					break;
				}
			}

			cell.append(
				dl_link, ' ',
				$('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
				'<br>'
			);
		});
	}
}, "what");

app.registerSource("bB", {
	url: function (book) {
		return 'https://baconbits.org/torrents.php?action=simple&filter_cat[3]=1&searchstr=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var filedata = cell.parent().data('filedata');
		var html = $(app.prepareHTML(response.responseText).replace(/href="([a-z]+)/gi, 'href="https://baconbits.org/$1'));
		var torrents = $('tr.torrent', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">bB (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var link = $('a', this).eq(3);
			var title = link.text().trim();
			var href = link.attr('href');
			var dl = $('a', this).eq(1).attr('href');
			var size = $('td', this).eq(4).text().replace(/,/g, '').trim();
			var text = $('td', this).eq(1).text().trim();
			var format = text.split('[') [2].split(']') [0].split(' / ') [0].trim();
			var retail = text.indexOf(' Retail!') > -1 ? ' <b>[R]</b> ' : '';

			var dl_link = $('<a href="' + dl + '"><b>' + format + '</b> ' + retail + size + '</a>');

			if (filedata.hasOwnProperty(format) && filedata[format] == size) {
				dl_link.css('color', '#c00');
			}

			var td = $('td', this).eq(1);
			$('.tags', td).remove();
			$('span:eq(0)', td).remove();

			cell.append(
				dl_link, ' ',
				$('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", td.text().trim()).tooltip(),
				'<br>'
			);
		});
	}
}, "baconbits");

app.registerSource("MAM", {
	url: function (book) {
		var query = book.searchAuthor + '"' + book.searchTitle + '"';
		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);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var row = cell.parent();
		var filedata = row.data('filedata');
		var filekeys = Object.keys(filedata);
		var html = $(app.prepareHTML(response.responseText).replace(/href="\//gi, 'href="https://www.myanonamouse.net/'));

		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);
		var torrents = row.data('book').formats.length > 0 ? $('tr:not(:eq(0)):not(:contains("GB")):icontains("' + title + '")', html) : $('tr:not(:eq(0))', html);

		cell.html('<h5><a href="' + response.finalUrl.replace('/js/loadSearch.php', '/browse.php') + '" target="_blank">MAM (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			$('td', this).eq(4).find('a').remove();
			var link = $('td', this).eq(2).find('a').first();
			var title = link.text().trim();
			var href = link.attr('href');
			var dl = $('td', this).eq(3).find('a').first().attr('href');
			var size = $('td', this).eq(4).text().trim().replace(/[\[\]]/g, '');
			var desc = $('td', this).eq(2).find('.torRowDesc').first();
			desc = desc.length > 0 ? desc.text().trim() : '';

			var dl_link = $('<a href="' + dl + '"><nobr>' + size + '</nobr></a>');

			for (i in filekeys) {
				if (parseFloat(filedata[filekeys[i]]).toFixed(1) == parseFloat(size)) {
					dl_link.css('color', '#c00');
					break;
				}
			}

			cell.append(
				'<a href="' + href + '" target="_blank">' + title + '</a><br>' + desc + ' ',
				dl_link, '<br><br>'
			);
		});
	}
}, "myanonamouse");

app.registerSource("WFL", {
	url: function (book) {
		return 'https://waffles.fm/browse.php?c86=1&c87=1&q=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var filedata = cell.parent().data('filedata');
		var filekeys = Object.keys(filedata);
		var html = $(app.prepareHTML(response.responseText).replace(/href="\//gi, 'href="https://waffles.fm/'));
		var torrents = $('#browsetable tr:not(:eq(0))', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">WFL (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			var href = $('a', this).eq(2).attr('href');
			var dl = $('a', this).eq(3).attr('href');
			var size = $('td', this).eq(5).text().trim().toUpperCase();
			size = size.slice(0, -2) + ' ' + size.slice(-2);
			var title = $('a', this).eq(2).text().trim();
			var text = title.toLowerCase();
			var formats = [];
			for (i in app.bookFormats) {
				if (text.indexOf(app.bookFormats[i]) > -1) {
					formats.push(app.bookFormats[i].toUpperCase());
					break;
				}
			}
			var dl_link = $('<a href="' + dl + '"><b>' + formats.join() + '</b> ' + size + '</a>');

			for (i in filekeys) {
				if (filedata[filekeys[i]] == size) {
					dl_link.css('color', '#c00');
					break;
				}
			}

			cell.append(
				dl_link, ' ',
				$('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
				'<br>'
			);
		});
	}
}, "waffles");

app.registerSource("TGZ", {
	url: function (book) {
		var author = book.searchAuthor.length > 0 ? book.searchAuthor + "and " : "";
		var query = author + '"' + book.searchTitle.replace(/\"/g, '') + '" and (pdf or epub or mobi or azw3) and not mp3';
		return 'https://thegeeks.bz/browse.php?incldead=1&nonboolean=3&titleonly=1&search=' + encodeURIComponent(query);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var filedata = cell.parent().data('filedata');
		var filekeys = Object.keys(filedata);
		var html = $(app.prepareHTML(response.responseText).replace(/href="([a-z]+)/gi, 'href="https://thegeeks.bz/$1'));
		var torrents = $('.ttable:not(:icontains("MP3")):not(:icontains("Webrip"))', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">TGZ (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			var href = $('a', this).eq(1).attr('href');
			var dl = $('td', this).eq(2).find('a').eq(1).attr('href');
			var size = $('td', this).eq(6).text().trim().toUpperCase();
			size = size.slice(0, -2) + ' ' + size.slice(-2);
			var title = $('a', this).eq(1).text().trim();
			var text = title.toLowerCase();

			var format = '';
			for (i in app.bookFormats) {
				if (text.indexOf(app.bookFormats[i]) > -1) {
					format = app.bookFormats[i].toUpperCase();
					break;
				}
			}

			var dl_link = $('<a href="' + dl + '"><b>' + format + '</b> ' + size + '</a>');

			for (i in filekeys) {
				if (filedata[filekeys[i]] == size) {
					dl_link.css('color', '#c00');
					break;
				}
			}

			cell.append(
				dl_link, ' ',
				$('<a href="' + href + '" target="_blank">&rarr;</a>').attr("title", title).tooltip(),
				'<br>'
			);
		});
	}
}, "thegeeks");

app.registerSource("RuTracker", {
	url: function (book) {
		var query = book.searchAuthor + book.searchTitle + ' ' + app.bookFormats.join('|');
		return 'http://rutracker.org/forum/tracker.php?nm=' + encodeURIComponent(query);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var filedata = cell.parent().data('filedata');
		var filekeys = Object.keys(filedata);
		var html = $(app.prepareHTML(response.responseText).replace(/href="viewtopic/gi, 'href="http://rutracker.org/forum/viewtopic'));
		var torrents = $('#tor-tbl tbody tr.hl-tr', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">TRU (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			var link = $('.t-title', this).first().find('a').first();
			var href = link.attr('href');
			var size = parseInt($('td', this).eq(5).find('u').text().trim());
			var i = Math.floor(Math.log(size) / Math.log(1024));
			size = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];

			for (i in filekeys) {
				if (filedata[filekeys[i]] == size) {
					size = "<span style=\"color:#c00\">"+size+"</span>";
					break;
				}
			}

			cell.append('<a href="' + href + '" target="_blank">' + link.text().trim() + '</a> - ' + size + '<br><br>');
		});
	}
}, "rutracker");

app.registerSource("BM", {
	url: function (book) {
		return 'http://www.bitme.org/browse.php?cat=6&incldead=1&search=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
	},
	onload: function (response) {
		if (!app.checkAuth(response)) return;

		var cell = $(response.context);
		var row = cell.parent();
		var filedata = row.data('filedata');
		var filekeys = Object.keys(filedata);
		var html = $(app.prepareHTML(response.responseText).replace(/href="/gi, 'href="http://www.bitme.org/'));

		var table = $("td.latest", html).first().closest("table");

		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);
		var torrents = row.data('book').formats.length > 0 ? $('tr:not(:eq(0)):not(:contains("GB")):icontains("' + title + '")', table) : $('tr:not(:eq(0))', table);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">BM (' + torrents.length + ')</a></h5>');

		torrents.each(function () {
			var i;
			var href = $('a', this).eq(1).attr('href');
			var dl = $('a', this).eq(3).attr('href');
			var size = $('td', this).eq(6).text().trim().toUpperCase();
			size = size.slice(0, -2) + ' ' + size.slice(-2);
			var title = $('a', this).eq(1).text().trim();

			var dl_link = $('<a href="' + dl + '">' + size + '</a>');

			for (i in filekeys) {
				if (filedata[filekeys[i]] == size) {
					dl_link.css('color', '#c00');
					break;
				}
			}

			cell.append(
				'<a href="' + href + '" target="_blank">' + title + '</a><br>',
				dl_link, "<br><br>"
			);
		});
	}
}, "bitme");

app.registerSource("Genesis", {
	url: function (book) {
		return 'http://gen.lib.rus.ec/search.php?open=0&view=simple&column=def&req=' + encodeURIComponent(book.searchAuthor + book.searchTitle);
	},
	onload: function (response) {
		var cell = $(response.context);

		var html = app.prepareHTML(response.responseText).split('<table width=100% cellspacing=1')[1];
		html = '<table width=100% cellspacing=1' + html;
		html = html.split('</table')[0];
		html += '</table>';
		html = $(html);

		var items = $('tr:not(:eq(0))', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">GEN (' + items.length + ')</a></h5>');

		items.each(function () {
			var row = $(this);
			var link = row.find("a").eq(1);
			link.find("font:last").remove();
			var title = link.text().trim();
			var dl = '<div>';
			dl += row.find('td').eq(8).text() + ' ' + row.find('td').eq(7).text() + ' ';
			dl += row.find('td').eq(9).html() + ' ';
			dl += row.find('td').eq(10).html() + ' ';
			dl += row.find('td').eq(11).html() + ' ';
			dl += row.find('td').eq(12).html() + ' ';
			dl += '</div>';
			cell.append($(dl).attr("title", title).tooltip({placement:"left"}));
		});
	}
});

app.registerSource("AvaxHome", {
	url: 'http://avxsearch.se/?c=5&exact=1&q=',
	onload: function (response) {
		var cell = $(response.context);
		var html = $(app.prepareHTML(response.responseText));
		var items = $('.article:not(:icontains("MP3"))', html);

		cell.html('<h5><a href="' + response.finalUrl + '" target="_blank">AVX (' + items.length + ')</a></h5>');

		items.each(function () {
			cell.append($('a.title-link', this).first().attr("target", "_blank"));

			var text = $('div.center', this).first().contents().filter(function () {
				return this.nodeType === 3;
			});
			text = text.text().trim();
			if (text.length > 0) {
				text = text.split(' | ');
				if (text.length > 4) {
					text.shift();
					text.shift();
				}
				text = text.join(' | ');
				cell.append('<br>', text);
			}

			cell.append('<br><br>');
		});
	}
});

app.run();