From db385fac26a541ac57f6d42c93d4d80fff47e183 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 7 May 2011 15:50:07 +0100 Subject: [PATCH 01/12] First cut at beam ebooks. Also fix format names in foyles and waterstones to match other stores. --- src/calibre/customize/builtins.py | 12 ++- .../gui2/store/beam_ebooks_de_plugin.py | 89 +++++++++++++++++++ src/calibre/gui2/store/foyles_uk_plugin.py | 2 +- .../gui2/store/waterstones_uk_plugin.py | 2 +- 4 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/calibre/gui2/store/beam_ebooks_de_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 36bcbdbfe2..ff9861c537 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1171,13 +1171,19 @@ class StoreFoylesUKStore(StoreBase): description = _('Foyles of London, online') actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' -class AmazonDEKindleStore(StoreBase): +class StoreAmazonDEKindleStore(StoreBase): name = 'Amazon DE Kindle' description = _('Kindle eBooks') actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' -plugins += [StoreAmazonKindleStore, AmazonDEKindleStore, StoreAmazonUKKindleStore, - StoreBaenWebScriptionStore, StoreBNStore, +class StoreBeamEBooksDEStore(StoreBase): + name = 'Beam EBooks DE' + description = _('Kindle eBooks') + actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' + +plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, + StoreAmazonUKKindleStore, + StoreBaenWebScriptionStore, StoreBNStore, StoreBeamEBooksDEStore, StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEHarlequinStoretore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py new file mode 100644 index 0000000000..18cff7a411 --- /dev/null +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.beam-ebooks.de' + url_details = 'http://www.beam-ebooks.de{0}' + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.beam-ebooks.de/suchergebnis.php?Type=&sw=' + urllib2.quote(query) + print(url) + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + print(doc) + for data in doc.xpath('//table[tr/td/div[@class="stil2"]]'): + print('here1') + if counter <= 0: + break + + id = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/@href')).strip() + print('here', id) + if not id: + continue + cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src')) + if cover_url: + cover_url = 'http://www.beam-ebooks.de' + cover_url + title = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/b/text()')) + author = ' '.join(data.xpath('./tr/td/div[@class="stil2"]/child::b/text()|./tr/td/div[@class="stil2"]/child::strong/text()')) + price = ''.join(data.xpath('./tr/td[3]/text()')) + print(data.xpath('./tr/td[3]/a/img/@alt')) + pdf = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)') + epub = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)') + mobi = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)') + print(id, cover_url, title, author, price, pdf, epub, mobi) + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.drm = SearchResult.DRM_UNLOCKED + s.detail_item = id + formats = [] + if epub: + formats.append('ePub') + if pdf: + formats.append('PDF') + if mobi: + formats.append('MOBI') + s.formats = ', '.join(formats) + + yield s diff --git a/src/calibre/gui2/store/foyles_uk_plugin.py b/src/calibre/gui2/store/foyles_uk_plugin.py index ca35fb6bb2..1a997cd671 100644 --- a/src/calibre/gui2/store/foyles_uk_plugin.py +++ b/src/calibre/gui2/store/foyles_uk_plugin.py @@ -73,6 +73,6 @@ class FoylesUKStore(BasicStoreConfig, StorePlugin): s.price = price s.detail_item = id s.drm = SearchResult.DRM_LOCKED - s.formats = 'EPUB' + s.formats = 'ePub' yield s diff --git a/src/calibre/gui2/store/waterstones_uk_plugin.py b/src/calibre/gui2/store/waterstones_uk_plugin.py index d422165c47..a5065128ba 100644 --- a/src/calibre/gui2/store/waterstones_uk_plugin.py +++ b/src/calibre/gui2/store/waterstones_uk_plugin.py @@ -76,7 +76,7 @@ class WaterstonesUKStore(BasicStoreConfig, StorePlugin): s.detail_item = id formats = [] if epub: - formats.append('EPUB') + formats.append('ePub') if pdf: formats.append('PDF') s.formats = ', '.join(formats) From e219f7214dca2b84b054077506867b2096298572 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 8 May 2011 13:36:08 +0100 Subject: [PATCH 02/12] Add epubbuy. Continue a bit with beam. --- src/calibre/customize/builtins.py | 11 ++- .../gui2/store/beam_ebooks_de_plugin.py | 5 +- src/calibre/gui2/store/epubbuy_de_plugin.py | 73 +++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/calibre/gui2/store/epubbuy_de_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 7f1705e8ff..5faa2de232 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1126,7 +1126,7 @@ class StoreEbookscomStore(StoreBase): description = _('The digital bookstore.') actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore' -class StoreEHarlequinStoretore(StoreBase): +class StoreEHarlequinStore(StoreBase): name = 'eHarlequin' description = _('entertain, enrich, inspire.') actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' @@ -1183,14 +1183,19 @@ class StoreAmazonDEKindleStore(StoreBase): class StoreBeamEBooksDEStore(StoreBase): name = 'Beam EBooks DE' - description = _('Kindle eBooks') + description = _('der eBook Shop') actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' +class StoreEPubBuyDEStore(StoreBase): + name = 'EPUBBuy DE' + description = _('EPUBReaders eBook Shop') + actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' + plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, StoreBeamEBooksDEStore, StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, - StoreEHarlequinStoretore, StoreFeedbooksStore, + StoreEPubBuyDEStore, StoreEHarlequinStore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, StoreWaterstonesUKStore] diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py index 18cff7a411..d5a9d8f12c 100644 --- a/src/calibre/gui2/store/beam_ebooks_de_plugin.py +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -23,8 +23,8 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - url = 'http://www.beam-ebooks.de' - url_details = 'http://www.beam-ebooks.de{0}' + url = 'http://www.affiliwelt.net/klick.php?log=no&prid=908&pid=2&bannerid=10072&url=http://www.beam-ebooks.de' + url_details = 'http://www.affiliwelt.net/klick.php?log=no&prid=908&pid=2&bannerid=10072&url=http://www.beam-ebooks.de{0}' if external or self.config.get('open_external', False): if detail_item: @@ -34,6 +34,7 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): detail_url = None if detail_item: detail_url = url_details.format(detail_item) + print(detail_url) d = WebStoreDialog(self.gui, url, parent, detail_url) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) diff --git a/src/calibre/gui2/store/epubbuy_de_plugin.py b/src/calibre/gui2/store/epubbuy_de_plugin.py new file mode 100644 index 0000000000..6b92e101f8 --- /dev/null +++ b/src/calibre/gui2/store/epubbuy_de_plugin.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib2 +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class EPubBuyDEStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.epubbuy.com/' + url_details = '{0}' + + if external or self.config.get('open_external', False): + if detail_item: + url = url_details.format(detail_item) + open_url(QUrl(url)) + else: + detail_url = None + if detail_item: + detail_url = url_details.format(detail_item) + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.epubbuy.com/search.php?search_query=' + urllib2.quote(query) + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//li[contains(@class, "ajax_block_product")]'): + if counter <= 0: + break + + id = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/@href')).strip() + if not id: + continue + cover_url = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/img/@src')) + if cover_url: + cover_url = 'http://www.epubbuy.com' + cover_url + title = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/@title')) + author = ''.join(data.xpath('./div[@class="center_block"]/a[2]/text()')) + price = ''.join(data.xpath('.//span[@class="price"]/text()')) + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.drm = SearchResult.DRM_UNLOCKED + s.detail_item = id + s.formats = 'ePub' + + yield s From a6261f3202d7caa46636f14f4d3437a28949b07b Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 8 May 2011 14:00:52 -0400 Subject: [PATCH 03/12] Borders plugin. --- src/calibre/customize/builtins.py | 8 +- src/calibre/gui2/store/borders_plugin.py | 94 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/store/borders_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index c1da8391e0..08049e48cf 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1116,6 +1116,11 @@ class StoreBeWriteStore(StoreBase): description = _('Publishers of fine books.') actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore' +class StoreBordersStore(StoreBase): + name = 'Borders' + description = _('Buy Books, Used Books, Music, DVDs & Blu-ray Online.') + actual_plugin = 'calibre.gui2.store.borders_plugin:BordersStore' + class StoreDieselEbooksStore(StoreBase): name = 'Diesel eBooks' description = _('World Famous eBook Store.') @@ -1183,7 +1188,8 @@ class AmazonDEKindleStore(StoreBase): plugins += [StoreAmazonKindleStore, AmazonDEKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, - StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, + StoreBeWriteStore, StoreBordersStore, + StoreDieselEbooksStore, StoreEbookscomStore, StoreEHarlequinStoretore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, diff --git a/src/calibre/gui2/store/borders_plugin.py b/src/calibre/gui2/store/borders_plugin.py new file mode 100644 index 0000000000..a610c2eaef --- /dev/null +++ b/src/calibre/gui2/store/borders_plugin.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import random +import re +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class BordersStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + #m_url = 'http://www.dpbolvw.net/' + #h_click = 'click-4879827-10762497' + #d_click = 'click-4879827-10772898' + # Use Kovid's affiliate id 30% of the time. + #if random.randint(1, 10) in (1, 2, 3): + # h_click = 'click-4913808-10762497' + # d_click = 'click-4913808-10772898' + + #url = m_url + h_click + url = 'http://www.borders.com/online/store/Landing?nav=5185+700152' + detail_url = None + if detail_item: + detail_url = 'http://www.borders.com/online/store/TitleDetail?sku=%s' % detail_item + #detail_url = m_url + d_click + detail_item + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) + else: + d = WebStoreDialog(self.gui, url, parent, detail_url) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.borders.com/online/store/SearchResults?type=44&simple=1&keyword=' + urllib.quote_plus(query) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//table[@class="browseResultsTable"]//tr'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//td[1]/a[1]/@href')) + if not id: + continue + id = re.search('(?<=sku=)\d+', id) + if not id: + continue + id = id.group() + + cover_url = ''.join(data.xpath('.//img[@class="jtip prod-item"]/@src')) + + title = ''.join(data.xpath('.//td[@align="left"][1]//a[1]//text()')) + author = ''.join(data.xpath('.//td[@align="left"][1]//a[2]/text()')) + + price = ''.join(data.xpath('.//div[@class="sale_price"]//text()')) + price = re.search('\$[\d.]+', price) + if not price: + continue + price = price.group() + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + #s.detail_item = '?url=http://www.kobobooks.com/' + id.strip() + s.drm = SearchResult.DRM_LOCKED + s.formats = 'EPUB' + + yield s From dd56bb7b6d9e32f811ee1a40eea630376f567ef8 Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 9 May 2011 18:13:59 -0400 Subject: [PATCH 04/12] Remove Borders store as they do not want to be included. --- src/calibre/customize/builtins.py | 7 +- src/calibre/gui2/store/borders_plugin.py | 94 ------------------------ 2 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 src/calibre/gui2/store/borders_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 08049e48cf..1ca51c2f81 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1116,11 +1116,6 @@ class StoreBeWriteStore(StoreBase): description = _('Publishers of fine books.') actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore' -class StoreBordersStore(StoreBase): - name = 'Borders' - description = _('Buy Books, Used Books, Music, DVDs & Blu-ray Online.') - actual_plugin = 'calibre.gui2.store.borders_plugin:BordersStore' - class StoreDieselEbooksStore(StoreBase): name = 'Diesel eBooks' description = _('World Famous eBook Store.') @@ -1188,7 +1183,7 @@ class AmazonDEKindleStore(StoreBase): plugins += [StoreAmazonKindleStore, AmazonDEKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, - StoreBeWriteStore, StoreBordersStore, + StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEHarlequinStoretore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, diff --git a/src/calibre/gui2/store/borders_plugin.py b/src/calibre/gui2/store/borders_plugin.py deleted file mode 100644 index a610c2eaef..0000000000 --- a/src/calibre/gui2/store/borders_plugin.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import (unicode_literals, division, absolute_import, print_function) - -__license__ = 'GPL 3' -__copyright__ = '2011, John Schember ' -__docformat__ = 'restructuredtext en' - -import random -import re -import urllib -from contextlib import closing - -from lxml import html - -from PyQt4.Qt import QUrl - -from calibre import browser, url_slash_cleaner -from calibre.gui2 import open_url -from calibre.gui2.store import StorePlugin -from calibre.gui2.store.basic_config import BasicStoreConfig -from calibre.gui2.store.search_result import SearchResult -from calibre.gui2.store.web_store_dialog import WebStoreDialog - -class BordersStore(BasicStoreConfig, StorePlugin): - - def open(self, parent=None, detail_item=None, external=False): - #m_url = 'http://www.dpbolvw.net/' - #h_click = 'click-4879827-10762497' - #d_click = 'click-4879827-10772898' - # Use Kovid's affiliate id 30% of the time. - #if random.randint(1, 10) in (1, 2, 3): - # h_click = 'click-4913808-10762497' - # d_click = 'click-4913808-10772898' - - #url = m_url + h_click - url = 'http://www.borders.com/online/store/Landing?nav=5185+700152' - detail_url = None - if detail_item: - detail_url = 'http://www.borders.com/online/store/TitleDetail?sku=%s' % detail_item - #detail_url = m_url + d_click + detail_item - - if external or self.config.get('open_external', False): - open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) - else: - d = WebStoreDialog(self.gui, url, parent, detail_url) - d.setWindowTitle(self.name) - d.set_tags(self.config.get('tags', '')) - d.exec_() - - def search(self, query, max_results=10, timeout=60): - url = 'http://www.borders.com/online/store/SearchResults?type=44&simple=1&keyword=' + urllib.quote_plus(query) - - br = browser() - - counter = max_results - with closing(br.open(url, timeout=timeout)) as f: - doc = html.fromstring(f.read()) - for data in doc.xpath('//table[@class="browseResultsTable"]//tr'): - if counter <= 0: - break - - id = ''.join(data.xpath('.//td[1]/a[1]/@href')) - if not id: - continue - id = re.search('(?<=sku=)\d+', id) - if not id: - continue - id = id.group() - - cover_url = ''.join(data.xpath('.//img[@class="jtip prod-item"]/@src')) - - title = ''.join(data.xpath('.//td[@align="left"][1]//a[1]//text()')) - author = ''.join(data.xpath('.//td[@align="left"][1]//a[2]/text()')) - - price = ''.join(data.xpath('.//div[@class="sale_price"]//text()')) - price = re.search('\$[\d.]+', price) - if not price: - continue - price = price.group() - - counter -= 1 - - s = SearchResult() - s.cover_url = cover_url - s.title = title.strip() - s.author = author.strip() - s.price = price.strip() - s.detail_item = id.strip() - #s.detail_item = '?url=http://www.kobobooks.com/' + id.strip() - s.drm = SearchResult.DRM_LOCKED - s.formats = 'EPUB' - - yield s From fb493ff8de2efb4cb0fbe8fbca0776074398e16e Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 9 May 2011 18:44:00 -0400 Subject: [PATCH 05/12] Store: List of stores that do not want to be included. Weightless Books plugin. --- src/calibre/customize/builtins.py | 27 ++++--- src/calibre/gui2/store/declined.txt | 5 ++ .../gui2/store/weightless_books_plugin.py | 76 +++++++++++++++++++ 3 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 src/calibre/gui2/store/declined.txt create mode 100644 src/calibre/gui2/store/weightless_books_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 1ca51c2f81..2c516f22c0 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1096,6 +1096,11 @@ class StoreAmazonKindleStore(StoreBase): description = _('Kindle books from Amazon') actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' +class StoreAmazonDEKindleStore(StoreBase): + name = 'Amazon DE Kindle' + description = _('Kindle eBooks') + actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' + class StoreAmazonUKKindleStore(StoreBase): name = 'Amazon UK Kindle' description = _('Kindle books from Amazon.uk') @@ -1136,6 +1141,11 @@ class StoreFeedbooksStore(StoreBase): description = _('Read anywhere.') actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore' +class StoreFoylesUKStore(StoreBase): + name = 'Foyles UK' + description = _('Foyles of London, online') + actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' + class StoreGutenbergStore(StoreBase): name = 'Project Gutenberg' description = _('The first producer of free ebooks.') @@ -1171,23 +1181,18 @@ class StoreWaterstonesUKStore(StoreBase): description = _('Feel every word') actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' -class StoreFoylesUKStore(StoreBase): - name = 'Foyles UK' - description = _('Foyles of London, online') - actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' +class StoreWeightlessBooksStore(StoreBase): + name = 'Weightless Books' + description = '(e)Books That Don\'t Weigh You Down' + actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' -class AmazonDEKindleStore(StoreBase): - name = 'Amazon DE Kindle' - description = _('Kindle eBooks') - actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' - -plugins += [StoreAmazonKindleStore, AmazonDEKindleStore, StoreAmazonUKKindleStore, +plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEHarlequinStoretore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, - StoreWaterstonesUKStore] + StoreWaterstonesUKStore, StoreWeightlessBooksStore] # }}} diff --git a/src/calibre/gui2/store/declined.txt b/src/calibre/gui2/store/declined.txt new file mode 100644 index 0000000000..2b0e5caed2 --- /dev/null +++ b/src/calibre/gui2/store/declined.txt @@ -0,0 +1,5 @@ +This is a list of stores that objected, declined +or asked not to be included in the store integration. + +* Borders (http://www.borders.com/) +* WH Smith (http://www.whsmith.co.uk/) diff --git a/src/calibre/gui2/store/weightless_books_plugin.py b/src/calibre/gui2/store/weightless_books_plugin.py new file mode 100644 index 0000000000..3fa1c76851 --- /dev/null +++ b/src/calibre/gui2/store/weightless_books_plugin.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class WeightlessBooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://weightlessbooks.com/' + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) + else: + d = WebStoreDialog(self.gui, url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://weightlessbooks.com/?s=' + urllib.quote_plus(query) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//li[@id="product"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="cover"]/a/@href')) + if not id: + continue + + cover_url = ''.join(data.xpath('.//div[@class="cover"]/a/img/@src')) + + price = ''.join(data.xpath('.//div[@class="buy_buttons"]/b[1]/text()')) + if not price: + continue + + formats = ', '.join(data.xpath('.//select[@class="eStore_variation"]//option//text()')) + formats = formats.upper() + + title = ''.join(data.xpath('.//h3/a/text()')) + author = ''.join(data.xpath('.//h3//text()')) + author = author.replace(title, '') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + s.formats = formats + + yield s From e1b0cfdacfdf772eaa9cf700191b431bc5d1f5df Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 9 May 2011 18:49:13 -0400 Subject: [PATCH 06/12] Store: Make mention of declined.txt in comments. --- src/calibre/gui2/store/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/store/__init__.py b/src/calibre/gui2/store/__init__.py index 214ede3372..35fa440b28 100644 --- a/src/calibre/gui2/store/__init__.py +++ b/src/calibre/gui2/store/__init__.py @@ -43,6 +43,8 @@ class StorePlugin(object): # {{{ The easiest way to handle affiliate money payouts is to randomly select between the author's affiliate id and calibre's affiliate id so that 70% of the time the author's id is used. + + See declined.txt for a list of stores that do not want to be included. ''' def __init__(self, gui, name): From 4a20c220f13fe4922ed83a566cb4b03b27146406 Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 9 May 2011 18:51:05 -0400 Subject: [PATCH 07/12] ... --- src/calibre/gui2/store/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/__init__.py b/src/calibre/gui2/store/__init__.py index 35fa440b28..d58ccbda84 100644 --- a/src/calibre/gui2/store/__init__.py +++ b/src/calibre/gui2/store/__init__.py @@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en' class StorePlugin(object): # {{{ ''' A plugin representing an online ebook repository (store). The store can - be a comercial store that sells ebooks or a source of free downloadable + be a commercial store that sells ebooks or a source of free downloadable ebooks. Note that this class is the base class for these plugins, however, to From f4878b5c7fa19fe85805263dce45a7f8d4b4a18e Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 9 May 2011 19:05:01 -0400 Subject: [PATCH 08/12] Store: Always sort stores in case-insentive alphabetical order. --- src/calibre/gui2/actions/store.py | 2 +- src/calibre/gui2/store/search/search.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 90f2cac3cd..1989250bc8 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -27,7 +27,7 @@ class StoreAction(InterfaceAction): self.store_menu.clear() self.store_menu.addAction(_('Search'), self.search) self.store_menu.addSeparator() - for n, p in self.gui.istores.items(): + for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()): self.store_menu.addAction(n, partial(self.open_store, p)) self.qaction.setMenu(self.store_menu) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 07d4afca54..62e4e97f11 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -47,7 +47,7 @@ class SearchDialog(QDialog, Ui_Dialog): # per search basis. stores_group_layout = QVBoxLayout() self.stores_group.setLayout(stores_group_layout) - for x in self.store_plugins: + for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): cbox = QCheckBox(x) cbox.setChecked(True) stores_group_layout.addWidget(cbox) From 6ebdda7aa54af32316cb962f7b815a692eef7f75 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 10 May 2011 12:58:54 +0100 Subject: [PATCH 09/12] Beam store, with working links --- src/calibre/gui2/store/beam_ebooks_de_plugin.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py index d5a9d8f12c..385bbc5456 100644 --- a/src/calibre/gui2/store/beam_ebooks_de_plugin.py +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -23,8 +23,8 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - url = 'http://www.affiliwelt.net/klick.php?log=no&prid=908&pid=2&bannerid=10072&url=http://www.beam-ebooks.de' - url_details = 'http://www.affiliwelt.net/klick.php?log=no&prid=908&pid=2&bannerid=10072&url=http://www.beam-ebooks.de{0}' + url = 'http://klick.affiliwelt.net/klick.php?bannerid=10072&pid=32307&prid=908' + url_details = 'http://klick.affiliwelt.net/klick.php?bannerid=10730&pid=32307&prid=908&prodid={0}' if external or self.config.get('open_external', False): if detail_item: @@ -34,7 +34,6 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): detail_url = None if detail_item: detail_url = url_details.format(detail_item) - print(detail_url) d = WebStoreDialog(self.gui, url, parent, detail_url) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) @@ -42,33 +41,28 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.beam-ebooks.de/suchergebnis.php?Type=&sw=' + urllib2.quote(query) - print(url) br = browser() counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) - print(doc) for data in doc.xpath('//table[tr/td/div[@class="stil2"]]'): - print('here1') if counter <= 0: break id = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/@href')).strip() - print('here', id) if not id: continue + id = id[7:] cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src')) if cover_url: cover_url = 'http://www.beam-ebooks.de' + cover_url title = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/b/text()')) author = ' '.join(data.xpath('./tr/td/div[@class="stil2"]/child::b/text()|./tr/td/div[@class="stil2"]/child::strong/text()')) price = ''.join(data.xpath('./tr/td[3]/text()')) - print(data.xpath('./tr/td[3]/a/img/@alt')) pdf = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)') epub = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)') mobi = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)') - print(id, cover_url, title, author, price, pdf, epub, mobi) counter -= 1 s = SearchResult() From c10349c5dd2b518cc7b6c8ab26dd102c28902a47 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 10 May 2011 15:18:16 +0100 Subject: [PATCH 10/12] Final versions of Beam and EPUBBuy. --- .../gui2/store/beam_ebooks_de_plugin.py | 19 ++++++++++++++----- src/calibre/gui2/store/epubbuy_de_plugin.py | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py index 385bbc5456..ee73607c57 100644 --- a/src/calibre/gui2/store/beam_ebooks_de_plugin.py +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -24,7 +24,8 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): url = 'http://klick.affiliwelt.net/klick.php?bannerid=10072&pid=32307&prid=908' - url_details = 'http://klick.affiliwelt.net/klick.php?bannerid=10730&pid=32307&prid=908&prodid={0}' + url_details = ('http://klick.affiliwelt.net/klick.php?' + 'bannerid=10730&pid=32307&prid=908&prodid={0}') if external or self.config.get('open_external', False): if detail_item: @@ -54,15 +55,23 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): if not id: continue id = id[7:] + print(id) cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src')) if cover_url: cover_url = 'http://www.beam-ebooks.de' + cover_url title = ''.join(data.xpath('./tr/td/div[@class="stil2"]/a/b/text()')) - author = ' '.join(data.xpath('./tr/td/div[@class="stil2"]/child::b/text()|./tr/td/div[@class="stil2"]/child::strong/text()')) + author = ' '.join(data.xpath('./tr/td/div[@class="stil2"]/' + 'child::b/text()' + '|' + './tr/td/div[@class="stil2"]/' + 'child::strong/text()')) price = ''.join(data.xpath('./tr/td[3]/text()')) - pdf = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)') - epub = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)') - mobi = data.xpath('boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)') + pdf = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)') + epub = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)') + mobi = data.xpath( + 'boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)') counter -= 1 s = SearchResult() diff --git a/src/calibre/gui2/store/epubbuy_de_plugin.py b/src/calibre/gui2/store/epubbuy_de_plugin.py index 6b92e101f8..242ef76793 100644 --- a/src/calibre/gui2/store/epubbuy_de_plugin.py +++ b/src/calibre/gui2/store/epubbuy_de_plugin.py @@ -23,8 +23,9 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class EPubBuyDEStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - url = 'http://www.epubbuy.com/' - url_details = '{0}' + url = 'http://klick.affiliwelt.net/klick.php?bannerid=47653&pid=32307&prid=2627' + url_details = ('http://klick.affiliwelt.net/klick.php?bannerid=47653' + '&pid=32307&prid=2627&prodid={0}') if external or self.config.get('open_external', False): if detail_item: @@ -50,13 +51,19 @@ class EPubBuyDEStore(BasicStoreConfig, StorePlugin): if counter <= 0: break - id = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/@href')).strip() + id = ''.join(data.xpath('./div[@class="center_block"]' + '/p[contains(text(), "artnr:")]/text()')).strip() if not id: continue - cover_url = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/img/@src')) + id = id[6:].strip() + if not id: + continue + cover_url = ''.join(data.xpath('./div[@class="center_block"]' + '/a[@class="product_img_link"]/img/@src')) if cover_url: cover_url = 'http://www.epubbuy.com' + cover_url - title = ''.join(data.xpath('./div[@class="center_block"]/a[@class="product_img_link"]/@title')) + title = ''.join(data.xpath('./div[@class="center_block"]' + '/a[@class="product_img_link"]/@title')) author = ''.join(data.xpath('./div[@class="center_block"]/a[2]/text()')) price = ''.join(data.xpath('.//span[@class="price"]/text()')) counter -= 1 From d9e0e361408aa64eecbcf683428d06c8ab10cac3 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 10 May 2011 16:18:13 +0100 Subject: [PATCH 11/12] Remove a print statement --- src/calibre/gui2/store/beam_ebooks_de_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/gui2/store/beam_ebooks_de_plugin.py b/src/calibre/gui2/store/beam_ebooks_de_plugin.py index ee73607c57..b589a8c310 100644 --- a/src/calibre/gui2/store/beam_ebooks_de_plugin.py +++ b/src/calibre/gui2/store/beam_ebooks_de_plugin.py @@ -55,7 +55,6 @@ class BeamEBooksDEStore(BasicStoreConfig, StorePlugin): if not id: continue id = id[7:] - print(id) cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src')) if cover_url: cover_url = 'http://www.beam-ebooks.de' + cover_url From 009090c99eee1fa83f028ac7dc8a21fbe8c62703 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 10 May 2011 18:23:18 -0400 Subject: [PATCH 12/12] Store: Add Wizards Tower Books plugin. --- src/calibre/customize/builtins.py | 7 +- .../gui2/store/wizards_tower_books_plugin.py | 88 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/store/wizards_tower_books_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 2c516f22c0..579403f6ba 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1186,6 +1186,11 @@ class StoreWeightlessBooksStore(StoreBase): description = '(e)Books That Don\'t Weigh You Down' actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' +class StoreWizardsTowerBooksStore(StoreBase): + name = 'Wizards Tower Books' + description = 'Wizard\'s Tower Press' + actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' + plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, StoreBeWriteStore, @@ -1193,6 +1198,6 @@ plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindl StoreEHarlequinStoretore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, - StoreWaterstonesUKStore, StoreWeightlessBooksStore] + StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore] # }}} diff --git a/src/calibre/gui2/store/wizards_tower_books_plugin.py b/src/calibre/gui2/store/wizards_tower_books_plugin.py new file mode 100644 index 0000000000..56bb00ff7e --- /dev/null +++ b/src/calibre/gui2/store/wizards_tower_books_plugin.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class WizardsTowerBooksStore(BasicStoreConfig, StorePlugin): + + url = 'http://www.wizardstowerbooks.com/' + + def open(self, parent=None, detail_item=None, external=False): + if detail_item: + detail_item = self.url + detail_item + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) + else: + d = WebStoreDialog(self.gui, self.url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.wizardstowerbooks.com/search.html?for=' + urllib.quote(query) + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//table[@class="gridp"]//td'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//span[@class="prti"]/a/@href')) + id = id.strip() + if not id: + continue + + cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src')) + cover_url = url_slash_cleaner(self.url + cover_url.strip()) + + price = ''.join(data.xpath('.//font[@class="selling_price"]//text()')) + price = price.strip() + if not price: + continue + + title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()')) + author = ''.join(data.xpath('.//p[@class="last"]/text()')) + a, b, author = author.partition(' by ') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + br = browser() + with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + + formats = ', '.join(idata.xpath('//select[@id="N1_"]//option//text()')) + search_result.formats = formats.upper() + + return True