Skip to content

Commit 4e76bd8

Browse files
committed
Open—instead of download—search results
1 parent 072e980 commit 4e76bd8

2 files changed

Lines changed: 115 additions & 113 deletions

File tree

__init__.py

Lines changed: 64 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,104 @@
1-
# -*- coding: utf-8 -*-
2-
31
from __future__ import (unicode_literals, division, absolute_import, print_function)
2+
3+
from calibre.customize import StoreBase
4+
from calibre.gui2 import open_url
5+
from calibre.gui2.store import StorePlugin
6+
from calibre.gui2.store.search_result import SearchResult
7+
from calibre.gui2.store.web_store_dialog import WebStoreDialog
8+
from PyQt5.Qt import QUrl
9+
10+
from .libgen_client import LibgenFictionClient
11+
412
store_version = 5 # Needed for dynamic plugin loading
513

614
__license__ = 'MIT'
715
__copyright__ = 'Fallacious Reasoning'
816
__docformat__ = 'restructuredtext en'
917

10-
#####################################################################
11-
# Plug-in base class
12-
#####################################################################
13-
14-
# from calibre.customize import InterfaceActionBase
15-
1618
PLUGIN_NAME = 'Libgen Fiction'
1719
PLUGIN_DESCRIPTION = 'Adds a Libgen Fiction search provider to Calibre'
18-
PLUGIN_VERSION_TUPLE = (0, 1, 0)
19-
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
2020
PLUGIN_AUTHORS = "Fallacious Reasoning (https://github.com/fallaciousreasoning/CalibreLibgenStore)"
21+
PLUGIN_VERSION = (0, 2, 0)
2122

22-
#####################################################################
23-
24-
# import base64
25-
# import mimetypes
26-
# import re
27-
# import urllib
28-
# import urllib2
29-
# from contextlib import closing
30-
31-
# from lxml import etree
32-
33-
from .libgen_client import LibgenFictionClient
34-
35-
# from calibre import browser, url_slash_cleaner
36-
# from calibre.constants import __appname__, __version__
37-
# from calibre.gui2.store.basic_config import BasicStoreConfig
38-
from calibre.gui2.store.search_result import SearchResult
39-
from calibre.gui2.store import StorePlugin
40-
41-
from calibre.customize import StoreBase
42-
43-
44-
libgen = LibgenFictionClient()
23+
class LibgenStore(StorePlugin):
24+
def genesis(self):
25+
'''
26+
Initialize the Libgen Client
27+
'''
4528

46-
def search(query, max_results=10, timeout=60):
47-
print('CalibreLibgenStore:search:query = {}'.format(query))
29+
print('LibgenStore:genesis: Initializing self.libgen')
30+
self.libgen = LibgenFictionClient()
4831

49-
libgen_results = libgen.search(query)
50-
for result in libgen_results.results[:min(max_results, len(libgen_results.results))]:
51-
print('CalibreLibgenStore:search:result.title = {}'.format(result.title))
32+
def search(self, query, max_results=10, timeout=60):
33+
'''
34+
Searches LibGen for Books. Since the mirror links are not direct
35+
downloads, it should not provide these as `s.downloads`.
36+
'''
5237

53-
s = SearchResult()
38+
print('LibgenStore:search: query = ', query)
5439

55-
s.title = result.title
56-
s.author = result.author
57-
s.series = result.series
58-
s.language = result.language
40+
libgen_results = self.libgen.search(query)
5941

60-
for download in result.downloads:
61-
print('CalibreLibgenStore:search:result.download.url = {}'.format(
62-
download.url))
42+
for result in libgen_results.results[:min(max_results, len(libgen_results.results))]:
43+
print('LibgenStore:search: result.title = ', result.title)
6344

64-
s.downloads['{} ({} {})'.format(
65-
download.format,
66-
download.size,
67-
download.unit
68-
)] = download.url
45+
for mirror in result.mirrors[0:1]: # Calibre only shows 1 anyway
46+
print('LibgenStore:search: result.mirror.url = ', mirror.url)
6947

70-
s.formats = ', '.join(s.downloads.keys())
71-
s.drm = SearchResult.DRM_UNLOCKED
72-
s.cover_url = result.image_url
48+
s = SearchResult()
7349

74-
# don't show results with no downloads
75-
if not s.formats:
76-
continue
50+
s.store_name = PLUGIN_NAME
51+
s.cover_url = result.image_url
52+
s.title = '{} ({}, {}{})'.format(
53+
result.title, result.language, mirror.size, mirror.unit)
54+
s.author = result.authors
55+
s.price = '0.00'
56+
s.detail_item = result.md5
57+
s.drm = SearchResult.DRM_UNLOCKED
58+
s.formats = mirror.format
59+
s.plugin_author = PLUGIN_AUTHORS
7760

78-
yield s
61+
print('LibgenStore:search: s = ', s, sep='\n')
7962

63+
yield s
8064

81-
class LibgenStore(StorePlugin):
82-
def search(self, query, max_results=10, timeout=60):
65+
def open(self, parent=None, detail_item=None, external=False):
8366
'''
84-
Searches LibGen for Books
67+
Open the specified item in the external, or Calibre's browser
8568
'''
86-
for result in search(query, max_results, timeout):
87-
yield result
8869

89-
if __name__ == '__main__':
90-
import sys
70+
print('LibgenStore:open: locals() = ', locals())
71+
72+
detail_url = (
73+
self.libgen.get_detail_url(detail_item)
74+
if detail_item
75+
else self.libgen.base_url
76+
)
77+
78+
print('LibgenStore:open: detail_url = ', detail_url)
9179

92-
query = ' '.join(sys.argv[1:]) if len(sys.argv) > 1 else "Stormlight Archive"
93-
for result in search(' '.join(sys.argv[1:])):
94-
print('=========================================================================================================\nTitle: {0}\nAuthor: {1}\nSeries: {2}\nLanguage: {3}\nDownloads: {4}'.format(result.title, result.author, result.series, result.language, len(result.downloads)))
80+
if external or self.config.get('open_external', False):
81+
open_url(QUrl(detail_url))
82+
else:
83+
d = WebStoreDialog(
84+
self.gui, self.libgen.base_url, parent, detail_url)
85+
d.setWindowTitle(self.name)
86+
d.set_tags(self.config.get('tags', ''))
87+
d.exec_()
9588

9689
class LibgenStoreWrapper(StoreBase):
9790
name = PLUGIN_NAME
9891
description = PLUGIN_DESCRIPTION
9992
supported_platforms = ['windows', 'osx', 'linux']
10093
author = PLUGIN_AUTHORS
101-
version = PLUGIN_VERSION_TUPLE
94+
version = PLUGIN_VERSION
10295
minimum_calibre_version = (1, 0, 0)
10396
affiliate = False
97+
drm_free_only = True
10498

10599
def load_actual_plugin(self, gui):
106100
'''
107101
This method must return the actual interface action plugin object.
108102
'''
109-
#mod, cls = self.actual_plugin.split(':')
110-
store = LibgenStore(gui, self.name)
111-
self.actual_plugin_object = store#getattr(importlib.import_module(mod), cls)(gui, self.name)
103+
self.actual_plugin_object = LibgenStore(gui, self.name)
112104
return self.actual_plugin_object

libgen_client.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,16 @@
11
from lxml import etree
2-
import re
2+
import random
33
import urllib
44

5-
MIRRORS = [
6-
"libgen.is",
7-
"gen.lib.rus.ec",
8-
"93.174.95.27",
9-
]
105

11-
BASE_URL = "http://{0}/".format(MIRRORS[0])
12-
LIBGEN_URL = "{0}fiction/".format(BASE_URL)
13-
14-
BOOK_ENDPOINT = "json.php?ids={0}&fields={1}"
15-
DOWNLOAD_URL = "get.php?md5={0}"
16-
SEARCH_URL = ""
17-
18-
ID_REGEX = "\?id=[0-9]+"
19-
20-
DEFAULT_FIELDS = "Title,Author,ID,MD5"
21-
22-
def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
23-
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)
246

257
def xpath(node, path):
268
tree = node.getroottree()
279
base_xpath = tree.getpath(node)
2810

2911
return tree.xpath(base_xpath + path)
3012

31-
class LibgenDownload:
13+
class LibgenMirror:
3214
def __init__(self, url, format, size, unit):
3315
self.url = url
3416
self.format = format
@@ -39,14 +21,16 @@ def __init__(self, url, format, size, unit):
3921
def parse(node, file_type, file_size, file_size_unit):
4022
url = node.get('href')
4123

42-
return LibgenDownload(url, file_type, file_size, file_size_unit)
24+
return LibgenMirror(url, file_type, file_size, file_size_unit)
4325

4426
class LibgenBook:
45-
def __init__(self, title, author, series, downloads, language, image_url):
27+
def __init__(self, title, authors, series, md5, mirrors, language,
28+
image_url):
4629
self.title = title
47-
self.author = author
30+
self.authors = authors
4831
self.series = series
49-
self.downloads = downloads
32+
self.md5 = md5
33+
self.mirrors = mirrors
5034
self.language = language
5135
self.image_url = image_url
5236

@@ -57,28 +41,37 @@ def parse(node):
5741
TITLE_XPATH = '/td[3]/a'
5842
LANGUAGE_XPATH = '/td[4]'
5943
FILE_XPATH = '/td[5]'
60-
DOWNLOADS_XPATH = '/td[6]//a'
44+
MIRRORS_XPATH = '/td[6]//a'
6145

62-
author_result = xpath(node, AUTHOR_XPATH)
63-
author = ' & '.join([result.text for result in author_result])\
64-
if len(author_result) > 0\
65-
else 'Unknown'
66-
series = xpath(node, SERIES_XPATH)[0].text
67-
title = xpath(node, TITLE_XPATH)[0].text
68-
language = xpath(node, LANGUAGE_XPATH)[0].text
46+
# Parse the Author(s) column into `authors`
47+
authors = ' & '.join([
48+
author.text for author in xpath(node, AUTHOR_XPATH)
49+
])
50+
51+
if len(authors) == 0:
52+
authors = 'Unknown'
53+
54+
# Parse File and Mirrors columns into a list of mirrors
6955
file_info = xpath(node, FILE_XPATH)[0].text.encode('utf-8')
7056
file_type, file_size = file_info.split(' / ')
7157
file_size, file_size_unit = file_size.split('\xc2\xa0')
7258

73-
downloads_nodes = xpath(node, DOWNLOADS_XPATH)
74-
downloads = [
75-
LibgenDownload.parse(n, file_type, file_size, file_size_unit)
76-
for n in downloads_nodes]
59+
mirrors = [
60+
LibgenMirror.parse(n, file_type, file_size, file_size_unit)
61+
for n in xpath(node, MIRRORS_XPATH)
62+
]
7763

78-
if not author and not title:
64+
# Parse other columns
65+
series = xpath(node, SERIES_XPATH)[0].text
66+
title = xpath(node, TITLE_XPATH)[0].text
67+
md5 = xpath(node, TITLE_XPATH)[0].get('href').split('/')[-1]
68+
language = xpath(node, LANGUAGE_XPATH)[0].text
69+
70+
if not authors or not title:
7971
return None
8072

81-
return LibgenBook(title, author, series, downloads, language, None)
73+
return LibgenBook(title, authors, series, md5, mirrors, language, None)
74+
8275

8376
class LibgenSearchResults:
8477
def __init__(self, results, total):
@@ -104,12 +97,24 @@ def parse(node):
10497

10598
return LibgenSearchResults(results, total)
10699

100+
107101
class LibgenFictionClient:
108-
def __init__(self, base_url=LIBGEN_URL):
109-
self.base_url = base_url
102+
def __init__(self, mirror=None):
103+
104+
MIRRORS = [
105+
"libgen.is",
106+
# "libgen.lc", # Still has the old-style search
107+
"gen.lib.rus.ec",
108+
"93.174.95.27",
109+
]
110+
111+
if mirror is None:
112+
self.base_url = "http://{}/fiction/".format(random.choice(MIRRORS))
113+
else:
114+
self.base_url = "http://{}/fiction/".format(mirror)
110115

111116
def search(self, query):
112-
url = self.base_url + SEARCH_URL
117+
url = self.base_url
113118
query_params = {
114119
'q': query,
115120
'criteria': '',
@@ -126,6 +131,11 @@ def search(self, query):
126131

127132
return LibgenSearchResults.parse(tree)
128133

134+
def get_detail_url(self, md5):
135+
detail_url = '{}{}'.format(self.base_url, md5)
136+
137+
return detail_url
138+
129139

130140
if __name__ == "__main__":
131141
client = LibgenFictionClient()

0 commit comments

Comments
 (0)