Get Books: When a free download is available for a search result, for example, for public domain books, allow direct download of the book into your calibre library.

This commit is contained in:
Kovid Goyal 2011-06-26 14:26:46 -06:00
commit 6747188703
67 changed files with 3528 additions and 428 deletions

View File

@ -5,7 +5,6 @@ www.ft.com
''' '''
import datetime import datetime
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class FinancialTimes_rss(BasicNewsRecipe): class FinancialTimes_rss(BasicNewsRecipe):

View File

@ -1148,7 +1148,7 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
class StoreAmazonKindleStore(StoreBase): class StoreAmazonKindleStore(StoreBase):
name = 'Amazon Kindle' name = 'Amazon Kindle'
description = u'Kindle books from Amazon.' description = u'Kindle books from Amazon.'
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' actual_plugin = 'calibre.gui2.store.stores.amazon_plugin:AmazonKindleStore'
headquarters = 'US' headquarters = 'US'
formats = ['KINDLE'] formats = ['KINDLE']
@ -1158,7 +1158,7 @@ class StoreAmazonDEKindleStore(StoreBase):
name = 'Amazon DE Kindle' name = 'Amazon DE Kindle'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Kindle Bücher von Amazon.' description = u'Kindle Bücher von Amazon.'
actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' actual_plugin = 'calibre.gui2.store.stores.amazon_de_plugin:AmazonDEKindleStore'
headquarters = 'DE' headquarters = 'DE'
formats = ['KINDLE'] formats = ['KINDLE']
@ -1168,7 +1168,7 @@ class StoreAmazonUKKindleStore(StoreBase):
name = 'Amazon UK Kindle' name = 'Amazon UK Kindle'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Kindle books from Amazon\'s UK web site. Also, includes French language ebooks.' description = u'Kindle books from Amazon\'s UK web site. Also, includes French language ebooks.'
actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' actual_plugin = 'calibre.gui2.store.stores.amazon_uk_plugin:AmazonUKKindleStore'
headquarters = 'UK' headquarters = 'UK'
formats = ['KINDLE'] formats = ['KINDLE']
@ -1177,7 +1177,7 @@ class StoreAmazonUKKindleStore(StoreBase):
class StoreArchiveOrgStore(StoreBase): class StoreArchiveOrgStore(StoreBase):
name = 'Archive.org' name = 'Archive.org'
description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.' description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.'
actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore' actual_plugin = 'calibre.gui2.store.stores.archive_org_plugin:ArchiveOrgStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1186,7 +1186,7 @@ class StoreArchiveOrgStore(StoreBase):
class StoreBaenWebScriptionStore(StoreBase): class StoreBaenWebScriptionStore(StoreBase):
name = 'Baen WebScription' name = 'Baen WebScription'
description = u'Sci-Fi & Fantasy brought to you by Jim Baen.' description = u'Sci-Fi & Fantasy brought to you by Jim Baen.'
actual_plugin = 'calibre.gui2.store.baen_webscription_plugin:BaenWebScriptionStore' actual_plugin = 'calibre.gui2.store.stores.baen_webscription_plugin:BaenWebScriptionStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1195,7 +1195,7 @@ class StoreBaenWebScriptionStore(StoreBase):
class StoreBNStore(StoreBase): class StoreBNStore(StoreBase):
name = 'Barnes and Noble' name = 'Barnes and Noble'
description = u'The world\'s largest book seller. As the ultimate destination for book lovers, Barnes & Noble.com offers an incredible array of content.' description = u'The world\'s largest book seller. As the ultimate destination for book lovers, Barnes & Noble.com offers an incredible array of content.'
actual_plugin = 'calibre.gui2.store.bn_plugin:BNStore' actual_plugin = 'calibre.gui2.store.stores.bn_plugin:BNStore'
headquarters = 'US' headquarters = 'US'
formats = ['NOOK'] formats = ['NOOK']
@ -1205,7 +1205,7 @@ class StoreBeamEBooksDEStore(StoreBase):
name = 'Beam EBooks DE' name = 'Beam EBooks DE'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks' description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks'
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' actual_plugin = 'calibre.gui2.store.stores.beam_ebooks_de_plugin:BeamEBooksDEStore'
drm_free_only = True drm_free_only = True
headquarters = 'DE' headquarters = 'DE'
@ -1215,7 +1215,7 @@ class StoreBeamEBooksDEStore(StoreBase):
class StoreBeWriteStore(StoreBase): class StoreBeWriteStore(StoreBase):
name = 'BeWrite Books' name = 'BeWrite Books'
description = u'Publishers of fine books. Highly selective and editorially driven. Does not offer: books for children or exclusively YA, erotica, swords-and-sorcery fantasy and space-opera-style science fiction. All other genres are represented.' description = u'Publishers of fine books. Highly selective and editorially driven. Does not offer: books for children or exclusively YA, erotica, swords-and-sorcery fantasy and space-opera-style science fiction. All other genres are represented.'
actual_plugin = 'calibre.gui2.store.bewrite_plugin:BeWriteStore' actual_plugin = 'calibre.gui2.store.stores.bewrite_plugin:BeWriteStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1224,7 +1224,7 @@ class StoreBeWriteStore(StoreBase):
class StoreDieselEbooksStore(StoreBase): class StoreDieselEbooksStore(StoreBase):
name = 'Diesel eBooks' name = 'Diesel eBooks'
description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.' description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.'
actual_plugin = 'calibre.gui2.store.diesel_ebooks_plugin:DieselEbooksStore' actual_plugin = 'calibre.gui2.store.stores.diesel_ebooks_plugin:DieselEbooksStore'
headquarters = 'US' headquarters = 'US'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1233,7 +1233,7 @@ class StoreDieselEbooksStore(StoreBase):
class StoreEbookscomStore(StoreBase): class StoreEbookscomStore(StoreBase):
name = 'eBooks.com' name = 'eBooks.com'
description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.' description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.'
actual_plugin = 'calibre.gui2.store.ebooks_com_plugin:EbookscomStore' actual_plugin = 'calibre.gui2.store.stores.ebooks_com_plugin:EbookscomStore'
headquarters = 'US' headquarters = 'US'
formats = ['EPUB', 'LIT', 'MOBI', 'PDF'] formats = ['EPUB', 'LIT', 'MOBI', 'PDF']
@ -1243,7 +1243,7 @@ class StoreEPubBuyDEStore(StoreBase):
name = 'EPUBBuy DE' name = 'EPUBBuy DE'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Bei EPUBBuy.com finden Sie ausschliesslich eBooks im weitverbreiteten EPUB-Format und ohne DRM. So haben Sie die freie Wahl, wo Sie Ihr eBook lesen: Tablet, eBook-Reader, Smartphone oder einfach auf Ihrem PC. So macht eBook-Lesen Spaß!' description = u'Bei EPUBBuy.com finden Sie ausschliesslich eBooks im weitverbreiteten EPUB-Format und ohne DRM. So haben Sie die freie Wahl, wo Sie Ihr eBook lesen: Tablet, eBook-Reader, Smartphone oder einfach auf Ihrem PC. So macht eBook-Lesen Spaß!'
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' actual_plugin = 'calibre.gui2.store.stores.epubbuy_de_plugin:EPubBuyDEStore'
drm_free_only = True drm_free_only = True
headquarters = 'DE' headquarters = 'DE'
@ -1254,7 +1254,7 @@ class StoreEBookShoppeUKStore(StoreBase):
name = 'ebookShoppe UK' name = 'ebookShoppe UK'
author = u'Charles Haley' author = u'Charles Haley'
description = u'We made this website in an attempt to offer the widest range of UK eBooks possible across and as many formats as we could manage.' description = u'We made this website in an attempt to offer the widest range of UK eBooks possible across and as many formats as we could manage.'
actual_plugin = 'calibre.gui2.store.ebookshoppe_uk_plugin:EBookShoppeUKStore' actual_plugin = 'calibre.gui2.store.stores.ebookshoppe_uk_plugin:EBookShoppeUKStore'
headquarters = 'UK' headquarters = 'UK'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1263,7 +1263,7 @@ class StoreEBookShoppeUKStore(StoreBase):
class StoreEHarlequinStore(StoreBase): class StoreEHarlequinStore(StoreBase):
name = 'eHarlequin' name = 'eHarlequin'
description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.' description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.'
actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' actual_plugin = 'calibre.gui2.store.stores.eharlequin_plugin:EHarlequinStore'
headquarters = 'CA' headquarters = 'CA'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1272,7 +1272,7 @@ class StoreEHarlequinStore(StoreBase):
class StoreEpubBudStore(StoreBase): class StoreEpubBudStore(StoreBase):
name = 'ePub Bud' name = 'ePub Bud'
description = 'Well, it\'s pretty much just "YouTube for Children\'s eBooks. A not-for-profit organization devoted to brining self published childrens books to the world.' description = 'Well, it\'s pretty much just "YouTube for Children\'s eBooks. A not-for-profit organization devoted to brining self published childrens books to the world.'
actual_plugin = 'calibre.gui2.store.epubbud_plugin:EpubBudStore' actual_plugin = 'calibre.gui2.store.stores.epubbud_plugin:EpubBudStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1281,7 +1281,7 @@ class StoreEpubBudStore(StoreBase):
class StoreFeedbooksStore(StoreBase): class StoreFeedbooksStore(StoreBase):
name = 'Feedbooks' name = 'Feedbooks'
description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.' description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.'
actual_plugin = 'calibre.gui2.store.feedbooks_plugin:FeedbooksStore' actual_plugin = 'calibre.gui2.store.stores.feedbooks_plugin:FeedbooksStore'
headquarters = 'FR' headquarters = 'FR'
formats = ['EPUB', 'MOBI', 'PDF'] formats = ['EPUB', 'MOBI', 'PDF']
@ -1290,7 +1290,7 @@ class StoreFoylesUKStore(StoreBase):
name = 'Foyles UK' name = 'Foyles UK'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Foyles of London\'s ebook store. Provides extensive range covering all subjects.' description = u'Foyles of London\'s ebook store. Provides extensive range covering all subjects.'
actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' actual_plugin = 'calibre.gui2.store.stores.foyles_uk_plugin:FoylesUKStore'
headquarters = 'UK' headquarters = 'UK'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1300,7 +1300,7 @@ class StoreGandalfStore(StoreBase):
name = 'Gandalf' name = 'Gandalf'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Księgarnia internetowa Gandalf.' description = u'Księgarnia internetowa Gandalf.'
actual_plugin = 'calibre.gui2.store.gandalf_plugin:GandalfStore' actual_plugin = 'calibre.gui2.store.stores.gandalf_plugin:GandalfStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1308,7 +1308,7 @@ class StoreGandalfStore(StoreBase):
class StoreGoogleBooksStore(StoreBase): class StoreGoogleBooksStore(StoreBase):
name = 'Google Books' name = 'Google Books'
description = u'Google Books' description = u'Google Books'
actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore' actual_plugin = 'calibre.gui2.store.stores.google_books_plugin:GoogleBooksStore'
headquarters = 'US' headquarters = 'US'
formats = ['EPUB', 'PDF', 'TXT'] formats = ['EPUB', 'PDF', 'TXT']
@ -1316,7 +1316,7 @@ class StoreGoogleBooksStore(StoreBase):
class StoreGutenbergStore(StoreBase): class StoreGutenbergStore(StoreBase):
name = 'Project Gutenberg' name = 'Project Gutenberg'
description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.' description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.'
actual_plugin = 'calibre.gui2.store.gutenberg_plugin:GutenbergStore' actual_plugin = 'calibre.gui2.store.stores.gutenberg_plugin:GutenbergStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1325,7 +1325,7 @@ class StoreGutenbergStore(StoreBase):
class StoreKoboStore(StoreBase): class StoreKoboStore(StoreBase):
name = 'Kobo' name = 'Kobo'
description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!' description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!'
actual_plugin = 'calibre.gui2.store.kobo_plugin:KoboStore' actual_plugin = 'calibre.gui2.store.stores.kobo_plugin:KoboStore'
headquarters = 'CA' headquarters = 'CA'
formats = ['EPUB'] formats = ['EPUB']
@ -1335,7 +1335,7 @@ class StoreLegimiStore(StoreBase):
name = 'Legimi' name = 'Legimi'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer' description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore' actual_plugin = 'calibre.gui2.store.stores.legimi_plugin:LegimiStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB'] formats = ['EPUB']
@ -1344,7 +1344,7 @@ class StoreLibreDEStore(StoreBase):
name = 'Libri DE' name = 'Libri DE'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Sicher Bücher, Hörbücher und Downloads online bestellen.' description = u'Sicher Bücher, Hörbücher und Downloads online bestellen.'
actual_plugin = 'calibre.gui2.store.libri_de_plugin:LibreDEStore' actual_plugin = 'calibre.gui2.store.stores.libri_de_plugin:LibreDEStore'
headquarters = 'DE' headquarters = 'DE'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1353,7 +1353,7 @@ class StoreLibreDEStore(StoreBase):
class StoreManyBooksStore(StoreBase): class StoreManyBooksStore(StoreBase):
name = 'ManyBooks' name = 'ManyBooks'
description = u'Public domain and creative commons works from many sources.' description = u'Public domain and creative commons works from many sources.'
actual_plugin = 'calibre.gui2.store.manybooks_plugin:ManyBooksStore' actual_plugin = 'calibre.gui2.store.stores.manybooks_plugin:ManyBooksStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1362,7 +1362,7 @@ class StoreManyBooksStore(StoreBase):
class StoreMobileReadStore(StoreBase): class StoreMobileReadStore(StoreBase):
name = 'MobileRead' name = 'MobileRead'
description = u'Ebooks handcrafted with the utmost care.' description = u'Ebooks handcrafted with the utmost care.'
actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore' actual_plugin = 'calibre.gui2.store.stores.mobileread.mobileread_plugin:MobileReadStore'
drm_free_only = True drm_free_only = True
headquarters = 'CH' headquarters = 'CH'
@ -1372,7 +1372,7 @@ class StoreNextoStore(StoreBase):
name = 'Nexto' name = 'Nexto'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Największy w Polsce sklep internetowy z audiobookami mp3, ebookami pdf oraz prasą do pobrania on-line.' description = u'Największy w Polsce sklep internetowy z audiobookami mp3, ebookami pdf oraz prasą do pobrania on-line.'
actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore' actual_plugin = 'calibre.gui2.store.stores.nexto_plugin:NextoStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1381,7 +1381,7 @@ class StoreNextoStore(StoreBase):
class StoreOpenBooksStore(StoreBase): class StoreOpenBooksStore(StoreBase):
name = 'Open Books' name = 'Open Books'
description = u'Comprehensive listing of DRM free ebooks from a variety of sources provided by users of calibre.' description = u'Comprehensive listing of DRM free ebooks from a variety of sources provided by users of calibre.'
actual_plugin = 'calibre.gui2.store.open_books_plugin:OpenBooksStore' actual_plugin = 'calibre.gui2.store.stores.open_books_plugin:OpenBooksStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1389,7 +1389,7 @@ class StoreOpenBooksStore(StoreBase):
class StoreOpenLibraryStore(StoreBase): class StoreOpenLibraryStore(StoreBase):
name = 'Open Library' name = 'Open Library'
description = u'One web page for every book ever published. The goal is to be a true online library. Over 20 million records from a variety of large catalogs as well as single contributions, with more on the way.' description = u'One web page for every book ever published. The goal is to be a true online library. Over 20 million records from a variety of large catalogs as well as single contributions, with more on the way.'
actual_plugin = 'calibre.gui2.store.open_library_plugin:OpenLibraryStore' actual_plugin = 'calibre.gui2.store.stores.open_library_plugin:OpenLibraryStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1398,7 +1398,7 @@ class StoreOpenLibraryStore(StoreBase):
class StoreOReillyStore(StoreBase): class StoreOReillyStore(StoreBase):
name = 'OReilly' name = 'OReilly'
description = u'Programming and tech ebooks from OReilly.' description = u'Programming and tech ebooks from OReilly.'
actual_plugin = 'calibre.gui2.store.oreilly_plugin:OReillyStore' actual_plugin = 'calibre.gui2.store.stores.oreilly_plugin:OReillyStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1407,7 +1407,7 @@ class StoreOReillyStore(StoreBase):
class StorePragmaticBookshelfStore(StoreBase): class StorePragmaticBookshelfStore(StoreBase):
name = 'Pragmatic Bookshelf' name = 'Pragmatic Bookshelf'
description = u'The Pragmatic Bookshelf\'s collection of programming and tech books avaliable as ebooks.' description = u'The Pragmatic Bookshelf\'s collection of programming and tech books avaliable as ebooks.'
actual_plugin = 'calibre.gui2.store.pragmatic_bookshelf_plugin:PragmaticBookshelfStore' actual_plugin = 'calibre.gui2.store.stores.pragmatic_bookshelf_plugin:PragmaticBookshelfStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1416,7 +1416,7 @@ class StorePragmaticBookshelfStore(StoreBase):
class StoreSmashwordsStore(StoreBase): class StoreSmashwordsStore(StoreBase):
name = 'Smashwords' name = 'Smashwords'
description = u'An ebook publishing and distribution platform for ebook authors, publishers and readers. Covers many genres and formats.' description = u'An ebook publishing and distribution platform for ebook authors, publishers and readers. Covers many genres and formats.'
actual_plugin = 'calibre.gui2.store.smashwords_plugin:SmashwordsStore' actual_plugin = 'calibre.gui2.store.stores.smashwords_plugin:SmashwordsStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1427,7 +1427,7 @@ class StoreVirtualoStore(StoreBase):
name = 'Virtualo' name = 'Virtualo'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.' description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.'
actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore' actual_plugin = 'calibre.gui2.store.stores.virtualo_plugin:VirtualoStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1436,7 +1436,7 @@ class StoreWaterstonesUKStore(StoreBase):
name = 'Waterstones UK' name = 'Waterstones UK'
author = 'Charles Haley' author = 'Charles Haley'
description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.' description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.'
actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' actual_plugin = 'calibre.gui2.store.stores.waterstones_uk_plugin:WaterstonesUKStore'
headquarters = 'UK' headquarters = 'UK'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1444,7 +1444,7 @@ class StoreWaterstonesUKStore(StoreBase):
class StoreWeightlessBooksStore(StoreBase): class StoreWeightlessBooksStore(StoreBase):
name = 'Weightless Books' name = 'Weightless Books'
description = u'An independent DRM-free ebooksite devoted to ebooks of all sorts.' description = u'An independent DRM-free ebooksite devoted to ebooks of all sorts.'
actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' actual_plugin = 'calibre.gui2.store.stores.weightless_books_plugin:WeightlessBooksStore'
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
@ -1454,7 +1454,7 @@ class StoreWHSmithUKStore(StoreBase):
name = 'WH Smith UK' name = 'WH Smith UK'
author = 'Charles Haley' author = 'Charles Haley'
description = u"Shop for savings on Books, discounted Magazine subscriptions and great prices on Stationery, Toys & Games" description = u"Shop for savings on Books, discounted Magazine subscriptions and great prices on Stationery, Toys & Games"
actual_plugin = 'calibre.gui2.store.whsmith_uk_plugin:WHSmithUKStore' actual_plugin = 'calibre.gui2.store.stores.whsmith_uk_plugin:WHSmithUKStore'
headquarters = 'UK' headquarters = 'UK'
formats = ['EPUB', 'PDF'] formats = ['EPUB', 'PDF']
@ -1462,7 +1462,7 @@ class StoreWHSmithUKStore(StoreBase):
class StoreWizardsTowerBooksStore(StoreBase): class StoreWizardsTowerBooksStore(StoreBase):
name = 'Wizards Tower Books' name = 'Wizards Tower Books'
description = u'A science fiction and fantasy publisher. Concentrates mainly on making out-of-print works available once more as e-books, and helping other small presses exploit the e-book market. Also publishes a small number of limited-print-run anthologies with a view to encouraging diversity in the science fiction and fantasy field.' description = u'A science fiction and fantasy publisher. Concentrates mainly on making out-of-print works available once more as e-books, and helping other small presses exploit the e-book market. Also publishes a small number of limited-print-run anthologies with a view to encouraging diversity in the science fiction and fantasy field.'
actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' actual_plugin = 'calibre.gui2.store.stores.wizards_tower_books_plugin:WizardsTowerBooksStore'
drm_free_only = True drm_free_only = True
headquarters = 'UK' headquarters = 'UK'
@ -1472,7 +1472,7 @@ class StoreWoblinkStore(StoreBase):
name = 'Woblink' name = 'Woblink'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Czytanie zdarza się wszędzie!' description = u'Czytanie zdarza się wszędzie!'
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore' actual_plugin = 'calibre.gui2.store.stores.woblink_plugin:WoblinkStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB'] formats = ['EPUB']
@ -1481,7 +1481,7 @@ class StoreZixoStore(StoreBase):
name = 'Zixo' name = 'Zixo'
author = u'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Księgarnia z ebookami oraz książkami audio. Aby otwierać książki w formacie Zixo należy zainstalować program dostępny na stronie księgarni. Umożliwia on m.in. dodawanie zakładek i dostosowywanie rozmiaru czcionki.' description = u'Księgarnia z ebookami oraz książkami audio. Aby otwierać książki w formacie Zixo należy zainstalować program dostępny na stronie księgarni. Umożliwia on m.in. dodawanie zakładek i dostosowywanie rozmiaru czcionki.'
actual_plugin = 'calibre.gui2.store.zixo_plugin:ZixoStore' actual_plugin = 'calibre.gui2.store.stores.zixo_plugin:ZixoStore'
headquarters = 'PL' headquarters = 'PL'
formats = ['PDF, ZIXO'] formats = ['PDF, ZIXO']

View File

@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__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 ArchiveOrgStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.archive.org/details/texts'
if detail_item:
detail_item = url_slash_cleaner('http://www.archive.org' + 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, 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):
query = query + ' AND mediatype:texts'
url = 'http://www.archive.org/search.php?query=' + 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('//td[@class="hitCell"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//a[@class="titleLink"]/@href'))
if not id:
continue
title = ''.join(data.xpath('.//a[@class="titleLink"]//text()'))
authors = data.xpath('.//text()')
if not authors:
continue
author = None
for a in authors:
if '-' in a:
author = a.replace('-', ' ').strip()
if author:
break
if not author:
continue
counter -= 1
s = SearchResult()
s.title = title.strip()
s.author = author.strip()
s.price = '$0.00'
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNLOCKED
yield s
def get_details(self, search_result, timeout):
url = url_slash_cleaner('http://www.archive.org' + search_result.detail_item)
br = browser()
with closing(br.open(url, timeout=timeout)) as nf:
idata = html.fromstring(nf.read())
formats = ', '.join(idata.xpath('//p[@id="dl" and @class="content"]//a/text()'))
search_result.formats = formats.upper()
return True

View File

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__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 EpubBudStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://epubbud.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):
'''
OPDS based search.
We really should get the catelog from http://pragprog.com/catalog.opds
and look for the application/opensearchdescription+xml entry.
Then get the opensearch description to get the search url and
format. However, we are going to be lazy and hard code it.
'''
url = 'http://www.epubbud.com/search.php?format=atom&q=' + urllib.quote_plus(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
# Use html instead of etree as html allows us
# to ignore the namespace easily.
doc = html.fromstring(f.read())
for data in doc.xpath('//entry'):
if counter <= 0:
break
id = ''.join(data.xpath('.//id/text()'))
if not id:
continue
cover_url = ''.join(data.xpath('.//link[@rel="http://opds-spec.org/thumbnail"]/@href'))
title = u''.join(data.xpath('.//title/text()'))
author = u''.join(data.xpath('.//author/name/text()'))
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = '$0.00'
s.detail_item = id.strip()
s.drm = SearchResult.DRM_UNLOCKED
s.formats = 'EPUB'
yield s

View File

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib2
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 FeedbooksStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://m.feedbooks.com/'
ext_url = 'http://feedbooks.com/'
if external or self.config.get('open_external', False):
if detail_item:
ext_url = ext_url + detail_item
open_url(QUrl(url_slash_cleaner(ext_url)))
else:
detail_url = None
if detail_item:
detail_url = url + 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://m.feedbooks.com/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('//ul[@class="m-list"]//li'):
if counter <= 0:
break
data = html.fromstring(html.tostring(data))
id = ''
id_a = data.xpath('//a[@class="buy"]')
if id_a:
id = id_a[0].get('href', None)
id = id.split('/')[-2]
id = '/item/' + id
else:
id_a = data.xpath('//a[@class="download"]')
if id_a:
id = id_a[0].get('href', None)
id = id.split('/')[-1]
id = id.split('.')[0]
id = '/book/' + id
if not id:
continue
title = ''.join(data.xpath('//h5//a/text()'))
author = ''.join(data.xpath('//h6//a/text()'))
price = ''.join(data.xpath('//a[@class="buy"]/text()'))
formats = 'EPUB'
if not price:
price = '$0.00'
formats = 'EPUB, MOBI, PDF'
cover_url = ''
cover_url_img = data.xpath('//img')
if cover_url_img:
cover_url = cover_url_img[0].get('src')
cover_url.split('?')[0]
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price.replace(' ', '').strip()
s.detail_item = id.strip()
s.formats = formats
yield s
def get_details(self, search_result, timeout):
url = 'http://m.feedbooks.com/'
br = browser()
with closing(br.open(url_slash_cleaner(url + search_result.detail_item), timeout=timeout)) as nf:
idata = html.fromstring(nf.read())
if idata.xpath('boolean(//div[contains(@class, "m-description-long")]//p[contains(., "DRM") or contains(b, "Protection")])'):
search_result.drm = SearchResult.DRM_LOCKED
else:
search_result.drm = SearchResult.DRM_UNLOCKED
return True

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import mimetypes
import urllib
from PyQt4.Qt import QUrl
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
from calibre.utils.opensearch import Client
class OpenSearchStore(StorePlugin):
open_search_url = ''
web_url = ''
def open(self, parent=None, detail_item=None, external=False):
if not hasattr(self, 'web_url'):
return
if external or self.config.get('open_external', False):
open_url(QUrl(detail_item if detail_item else self.web_url))
else:
d = WebStoreDialog(self.gui, self.web_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):
if not hasattr(self, 'open_search_url'):
return
client = Client(self.open_search_url)
results = client.search(urllib.quote_plus(query), max_results)
counter = max_results
for r in results:
if counter <= 0:
break
counter -= 1
s = SearchResult()
s.detail_item = r.get('id', '')
links = r.get('links', None)
for l in links:
if l.get('rel', None):
if l['rel'] in ('http://opds-spec.org/thumbnail', 'http://opds-spec.org/image/thumbnail'):
s.cover_url = l.get('href', '')
elif l['rel'] == u'http://opds-spec.org/acquisition/buy':
s.detail_item = l.get('href', s.detail_item)
elif l['rel'] == u'http://opds-spec.org/acquisition':
mime = l.get('type', '')
if mime:
ext = mimetypes.guess_extension(mime)
if ext:
ext = ext[1:].upper()
s.downloads[ext] = l.get('href', '')
s.formats = ', '.join(s.downloads.keys())
s.title = r.get('title', '')
s.author = r.get('author', '')
s.price = r.get('price', '')
yield s

View File

@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__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 PragmaticBookshelfStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://pragprog.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):
'''
OPDS based search.
We really should get the catelog from http://pragprog.com/catalog.opds
and look for the application/opensearchdescription+xml entry.
Then get the opensearch description to get the search url and
format. However, we are going to be lazy and hard code it.
'''
url = 'http://pragprog.com/catalog/search?q=' + urllib.quote_plus(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
# Use html instead of etree as html allows us
# to ignore the namespace easily.
doc = html.fromstring(f.read())
for data in doc.xpath('//entry'):
if counter <= 0:
break
id = ''.join(data.xpath('.//link[@rel="http://opds-spec.org/acquisition/buy"]/@href'))
if not id:
continue
price = ''.join(data.xpath('.//price/@currencycode')).strip()
price += ' '
price += ''.join(data.xpath('.//price/text()')).strip()
if not price.strip():
continue
cover_url = ''.join(data.xpath('.//link[@rel="http://opds-spec.org/cover"]/@href'))
title = ''.join(data.xpath('.//title/text()'))
author = ''.join(data.xpath('.//author//text()'))
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 = 'EPUB, PDF, MOBI'
yield s

View File

@ -45,6 +45,7 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
self.author_box.setText('') self.author_box.setText('')
self.price_box.setText('') self.price_box.setText('')
self.format_box.setText('') self.format_box.setText('')
self.download_combo.setCurrentIndex(0)
self.affiliate_combo.setCurrentIndex(0) self.affiliate_combo.setCurrentIndex(0)
def tokens(self, raw): def tokens(self, raw):
@ -119,6 +120,9 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
format = unicode(self.format_box.text()).strip() format = unicode(self.format_box.text()).strip()
if format: if format:
ans.append('format:"' + self.mc + format + '"') ans.append('format:"' + self.mc + format + '"')
download = unicode(self.download_combo.currentText()).strip()
if download:
ans.append('download:' + download)
affiliate = unicode(self.affiliate_combo.currentText()).strip() affiliate = unicode(self.affiliate_combo.currentText()).strip()
if affiliate: if affiliate:
ans.append('affiliate:' + affiliate) ans.append('affiliate:' + affiliate)

View File

@ -226,7 +226,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2"> <item row="8" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_6"> <layout class="QHBoxLayout" name="horizontalLayout_6">
<item> <item>
<widget class="QPushButton" name="clear_button"> <widget class="QPushButton" name="clear_button">
@ -244,7 +244,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="6" column="1"> <item row="7" column="1">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -283,14 +283,14 @@
<item row="3" column="1"> <item row="3" column="1">
<widget class="EnLineEdit" name="price_box"/> <widget class="EnLineEdit" name="price_box"/>
</item> </item>
<item row="5" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
<string>Affiliate:</string> <string>Affiliate:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="6" column="1">
<widget class="QComboBox" name="affiliate_combo"> <widget class="QComboBox" name="affiliate_combo">
<item> <item>
<property name="text"> <property name="text">
@ -309,6 +309,32 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Download:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="download_combo">
<item>
<property name="text">
<string/>
</property>
</item>
<item>
<property name="text">
<string>true</string>
</property>
</item>
<item>
<property name="text">
<string>false</string>
</property>
</item>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

View File

@ -33,7 +33,7 @@ class Matches(QAbstractItemModel):
total_changed = pyqtSignal(int) total_changed = pyqtSignal(int)
HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store'), ''] HEADERS = [_('Cover'), _('Title'), _('Price'), _('DRM'), _('Store'), _('Download'), _('Affiliate')]
HTML_COLS = (1, 4) HTML_COLS = (1, 4)
def __init__(self, cover_thread_count=2, detail_thread_count=4): def __init__(self, cover_thread_count=2, detail_thread_count=4):
@ -47,6 +47,8 @@ class Matches(QAbstractItemModel):
Qt.SmoothTransformation) Qt.SmoothTransformation)
self.DONATE_ICON = QPixmap(I('donate.png')).scaledToHeight(16, self.DONATE_ICON = QPixmap(I('donate.png')).scaledToHeight(16,
Qt.SmoothTransformation) Qt.SmoothTransformation)
self.DOWNLOAD_ICON = QPixmap(I('arrow-down.png')).scaledToHeight(16,
Qt.SmoothTransformation)
# All matches. Used to determine the order to display # All matches. Used to determine the order to display
# self.matches because the SearchFilter returns # self.matches because the SearchFilter returns
@ -181,9 +183,11 @@ class Matches(QAbstractItemModel):
elif result.drm == SearchResult.DRM_UNKNOWN: elif result.drm == SearchResult.DRM_UNKNOWN:
return QVariant(self.DRM_UNKNOWN_ICON) return QVariant(self.DRM_UNKNOWN_ICON)
if col == 5: if col == 5:
if result.downloads:
return QVariant(self.DOWNLOAD_ICON)
if col == 6:
if result.affiliate: if result.affiliate:
return QVariant(self.DONATE_ICON) return QVariant(self.DONATE_ICON)
return NONE
elif role == Qt.ToolTipRole: elif role == Qt.ToolTipRole:
if col == 1: if col == 1:
return QVariant('<p>%s</p>' % result.title) return QVariant('<p>%s</p>' % result.title)
@ -199,6 +203,9 @@ class Matches(QAbstractItemModel):
elif col == 4: elif col == 4:
return QVariant('<p>%s</p>' % result.formats) return QVariant('<p>%s</p>' % result.formats)
elif col == 5: elif col == 5:
if result.downloads:
return QVariant('<p>' + _('The following formats can be downloaded directly: %s.') % ', '.join(result.downloads.keys()) + '</p>')
elif col == 6:
if result.affiliate: if result.affiliate:
return QVariant('<p>' + _('Buying from this store supports the calibre developer: %s.') % result.plugin_author + '</p>') return QVariant('<p>' + _('Buying from this store supports the calibre developer: %s.') % result.plugin_author + '</p>')
elif role == Qt.SizeHintRole: elif role == Qt.SizeHintRole:
@ -221,6 +228,11 @@ class Matches(QAbstractItemModel):
elif col == 4: elif col == 4:
text = result.store_name text = result.store_name
elif col == 5: elif col == 5:
if result.downloads:
text = 'a'
else:
text = 'b'
elif col == 6:
if result.affiliate: if result.affiliate:
text = 'a' text = 'a'
else: else:
@ -257,6 +269,8 @@ class SearchFilter(SearchQueryParser):
'author', 'author',
'authors', 'authors',
'cover', 'cover',
'download',
'downloads',
'drm', 'drm',
'format', 'format',
'formats', 'formats',
@ -282,6 +296,8 @@ class SearchFilter(SearchQueryParser):
location = location.lower().strip() location = location.lower().strip()
if location == 'authors': if location == 'authors':
location = 'author' location = 'author'
elif location == 'downloads':
location = 'download'
elif location == 'formats': elif location == 'formats':
location = 'format' location = 'format'
@ -308,12 +324,13 @@ class SearchFilter(SearchQueryParser):
'author': lambda x: x.author.lower(), 'author': lambda x: x.author.lower(),
'cover': attrgetter('cover_url'), 'cover': attrgetter('cover_url'),
'drm': attrgetter('drm'), 'drm': attrgetter('drm'),
'download': attrgetter('downloads'),
'format': attrgetter('formats'), 'format': attrgetter('formats'),
'price': lambda x: comparable_price(x.price), 'price': lambda x: comparable_price(x.price),
'store': lambda x: x.store_name.lower(), 'store': lambda x: x.store_name.lower(),
'title': lambda x: x.title.lower(), 'title': lambda x: x.title.lower(),
} }
for x in ('author', 'format'): for x in ('author', 'download', 'format'):
q[x+'s'] = q[x] q[x+'s'] = q[x]
for sr in self.srs: for sr in self.srs:
for locvalue in locations: for locvalue in locations:
@ -347,7 +364,7 @@ class SearchFilter(SearchQueryParser):
matches.add(sr) matches.add(sr)
continue continue
# this is bool or treated as bool, so can't match below. # this is bool or treated as bool, so can't match below.
if locvalue in ('affiliate', 'drm'): if locvalue in ('affiliate', 'drm', 'download', 'downloads'):
continue continue
try: try:
### Can't separate authors because comma is used for name sep and author sep ### Can't separate authors because comma is used for name sep and author sep

View File

@ -6,13 +6,18 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>' __copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QTreeView) from functools import partial
from PyQt4.Qt import (pyqtSignal, QMenu, QTreeView)
from calibre.gui2.metadata.single_download import RichTextDelegate from calibre.gui2.metadata.single_download import RichTextDelegate
from calibre.gui2.store.search.models import Matches from calibre.gui2.store.search.models import Matches
class ResultsView(QTreeView): class ResultsView(QTreeView):
download_requested = pyqtSignal(object)
open_requested = pyqtSignal(object)
def __init__(self, *args): def __init__(self, *args):
QTreeView.__init__(self,*args) QTreeView.__init__(self,*args)
@ -24,3 +29,18 @@ class ResultsView(QTreeView):
for i in self._model.HTML_COLS: for i in self._model.HTML_COLS:
self.setItemDelegateForColumn(i, self.rt_delegate) self.setItemDelegateForColumn(i, self.rt_delegate)
def contextMenuEvent(self, event):
index = self.indexAt(event.pos())
if not index.isValid():
return
result = self.model().get_result(index)
menu = QMenu()
da = menu.addAction(_('Download...'), partial(self.download_requested.emit, result))
if not result.downloads:
da.setEnabled(False)
menu.addSeparator()
menu.addAction(_('Goto in store...'), partial(self.open_requested.emit, result))
menu.exec_(event.globalPos())

View File

@ -14,6 +14,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel,
QComboBox) QComboBox)
from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2 import JSONConfig, info_dialog
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
@ -72,7 +73,9 @@ class SearchDialog(QDialog, Ui_Dialog):
self.search.clicked.connect(self.do_search) self.search.clicked.connect(self.do_search)
self.checker.timeout.connect(self.get_results) self.checker.timeout.connect(self.get_results)
self.progress_checker.timeout.connect(self.check_progress) self.progress_checker.timeout.connect(self.check_progress)
self.results_view.activated.connect(self.open_store) self.results_view.activated.connect(self.result_item_activated)
self.results_view.download_requested.connect(self.download_book)
self.results_view.open_requested.connect(self.open_store)
self.results_view.model().total_changed.connect(self.update_book_total) self.results_view.model().total_changed.connect(self.update_book_total)
self.select_all_stores.clicked.connect(self.stores_select_all) self.select_all_stores.clicked.connect(self.stores_select_all)
self.select_invert_stores.clicked.connect(self.stores_select_invert) self.select_invert_stores.clicked.connect(self.stores_select_invert)
@ -129,11 +132,15 @@ class SearchDialog(QDialog, Ui_Dialog):
# Title / Author # Title / Author
self.results_view.setColumnWidth(1,int(total*.40)) self.results_view.setColumnWidth(1,int(total*.40))
# Price # Price
self.results_view.setColumnWidth(2,int(total*.20)) self.results_view.setColumnWidth(2,int(total*.12))
# DRM # DRM
self.results_view.setColumnWidth(3, int(total*.15)) self.results_view.setColumnWidth(3, int(total*.15))
# Store / Formats # Store / Formats
self.results_view.setColumnWidth(4, int(total*.25)) self.results_view.setColumnWidth(4, int(total*.25))
# Download
self.results_view.setColumnWidth(5, 20)
# Affiliate
self.results_view.setColumnWidth(6, 20)
def do_search(self): def do_search(self):
# Stop all running threads. # Stop all running threads.
@ -183,7 +190,7 @@ class SearchDialog(QDialog, Ui_Dialog):
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query) query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
query = query.replace('%s:' % loc, '') query = query.replace('%s:' % loc, '')
# Remove the prefix and search text. # Remove the prefix and search text.
for loc in ('cover', 'drm', 'format', 'formats', 'price', 'store'): for loc in ('cover', 'download', 'downloads', 'drm', 'format', 'formats', 'price', 'store'):
query = re.sub(r'%s:"[^"]"' % loc, '', query) query = re.sub(r'%s:"[^"]"' % loc, '', query)
query = re.sub(r'%s:[^\s]*' % loc, '', query) query = re.sub(r'%s:[^\s]*' % loc, '', query)
# Remove logic. # Remove logic.
@ -330,8 +337,21 @@ class SearchDialog(QDialog, Ui_Dialog):
def update_book_total(self, total): def update_book_total(self, total):
self.total.setText('%s' % total) self.total.setText('%s' % total)
def open_store(self, index): def result_item_activated(self, index):
result = self.results_view.model().get_result(index) result = self.results_view.model().get_result(index)
if result.downloads:
self.download_book(result)
else:
self.open_store(result)
def download_book(self, result):
d = ChooseFormatDialog(self, _('Choose format to download to your library.'), result.downloads.keys())
if d.exec_() == d.Accepted:
ext = d.format()
self.gui.download_ebook(result.downloads[ext])
def open_store(self, result):
self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked()) self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
def check_progress(self): def check_progress(self):

View File

@ -22,6 +22,9 @@ class SearchResult(object):
self.detail_item = '' self.detail_item = ''
self.drm = None self.drm = None
self.formats = '' self.formats = ''
# key = format in upper case.
# value = url to download the file.
self.downloads = {}
self.affiliate = False self.affiliate = False
self.plugin_author = '' self.plugin_author = ''

View File

@ -0,0 +1,3 @@
'''
All store plugins are placed here.
'''

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.opensearch_store import OpenSearchStore
from calibre.gui2.store.search_result import SearchResult
class ArchiveOrgStore(BasicStoreConfig, OpenSearchStore):
open_search_url = 'http://bookserver.archive.org/catalog/opensearch.xml'
web_url = 'http://www.archive.org/details/texts'
# http://bookserver.archive.org/catalog/
def search(self, query, max_results=10, timeout=60):
for s in OpenSearchStore.search(self, query, max_results, timeout):
s.detail_item = 'http://www.archive.org/details/' + s.detail_item.split(':')[-1]
s.price = '$0.00'
s.drm = SearchResult.DRM_UNLOCKED
yield s
'''
def get_details(self, search_result, timeout):
br = browser()
with closing(br.open(search_result.detail_item, timeout=timeout)) as nf:
idata = html.fromstring(nf.read())
formats = ', '.join(idata.xpath('//p[@id="dl" and @class="content"]//a/text()'))
search_result.formats = formats.upper()
return True
'''

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.opensearch_store import OpenSearchStore
from calibre.gui2.store.search_result import SearchResult
class EpubBudStore(BasicStoreConfig, OpenSearchStore):
open_search_url = 'http://www.epubbud.com/feeds/opensearch.xml'
web_url = 'http://www.epubbud.com/'
# http://www.epubbud.com/feeds/catalog.atom
def search(self, query, max_results=10, timeout=60):
for s in OpenSearchStore.search(self, query, max_results, timeout):
s.price = '$0.00'
s.drm = SearchResult.DRM_UNLOCKED
s.formats = 'EPUB'
# Download links are broken for this store.
s.downloads = {}
yield s

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.opensearch_store import OpenSearchStore
from calibre.gui2.store.search_result import SearchResult
class FeedbooksStore(BasicStoreConfig, OpenSearchStore):
open_search_url = 'http://assets0.feedbooks.net/opensearch.xml?t=1253087147'
web_url = 'http://feedbooks.com/'
# http://www.feedbooks.com/catalog
def search(self, query, max_results=10, timeout=60):
for s in OpenSearchStore.search(self, query, max_results, timeout):
if s.downloads:
s.drm = SearchResult.DRM_UNLOCKED
s.price = '$0.00'
else:
s.drm = SearchResult.DRM_LOCKED
s.formats = 'EPUB'
yield s

View File

@ -79,7 +79,6 @@ class ManyBooksStore(BasicStoreConfig, StorePlugin):
cover_name = cover_name.replace('etext', '') cover_name = cover_name.replace('etext', '')
cover_id = id.split('.')[0] cover_id = id.split('.')[0]
cover_url = 'http://www.manybooks.net/images/' + id[0] + '/' + cover_name + '/' + cover_id + '-thumb.jpg' cover_url = 'http://www.manybooks.net/images/' + id[0] + '/' + cover_name + '/' + cover_id + '-thumb.jpg'
print(cover_url)
counter -= 1 counter -= 1

View File

@ -10,7 +10,7 @@ import re
from PyQt4.Qt import (QDialog, QDialogButtonBox) from PyQt4.Qt import (QDialog, QDialogButtonBox)
from calibre.gui2.store.mobileread.adv_search_builder_ui import Ui_Dialog from calibre.gui2.store.stores.mobileread.adv_search_builder_ui import Ui_Dialog
from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH
class AdvSearchBuilderDialog(QDialog, Ui_Dialog): class AdvSearchBuilderDialog(QDialog, Ui_Dialog):

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QDialog from PyQt4.Qt import QDialog
from calibre.gui2.store.mobileread.cache_progress_dialog_ui import Ui_Dialog from calibre.gui2.store.stores.mobileread.cache_progress_dialog_ui import Ui_Dialog
class CacheProgressDialog(QDialog, Ui_Dialog): class CacheProgressDialog(QDialog, Ui_Dialog):

View File

@ -15,10 +15,10 @@ from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog from calibre.gui2.store.web_store_dialog import WebStoreDialog
from calibre.gui2.store.mobileread.models import SearchFilter from calibre.gui2.store.stores.mobileread.models import SearchFilter
from calibre.gui2.store.mobileread.cache_progress_dialog import CacheProgressDialog from calibre.gui2.store.stores.mobileread.cache_progress_dialog import CacheProgressDialog
from calibre.gui2.store.mobileread.cache_update_thread import CacheUpdateThread from calibre.gui2.store.stores.mobileread.cache_update_thread import CacheUpdateThread
from calibre.gui2.store.mobileread.store_dialog import MobileReadStoreDialog from calibre.gui2.store.stores.mobileread.store_dialog import MobileReadStoreDialog
class MobileReadStore(BasicStoreConfig, StorePlugin): class MobileReadStore(BasicStoreConfig, StorePlugin):

View File

@ -9,9 +9,9 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import (Qt, QDialog, QIcon, QComboBox) from PyQt4.Qt import (Qt, QDialog, QIcon, QComboBox)
from calibre.gui2.store.mobileread.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.stores.mobileread.adv_search_builder import AdvSearchBuilderDialog
from calibre.gui2.store.mobileread.models import BooksModel from calibre.gui2.store.stores.mobileread.models import BooksModel
from calibre.gui2.store.mobileread.store_dialog_ui import Ui_Dialog from calibre.gui2.store.stores.mobileread.store_dialog_ui import Ui_Dialog
class MobileReadStoreDialog(QDialog, Ui_Dialog): class MobileReadStoreDialog(QDialog, Ui_Dialog):

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.opensearch_store import OpenSearchStore
from calibre.gui2.store.search_result import SearchResult
class PragmaticBookshelfStore(BasicStoreConfig, OpenSearchStore):
open_search_url = 'http://pragprog.com/catalog/search-description'
web_url = 'http://pragprog.com/'
# http://pragprog.com/catalog.opds
def search(self, query, max_results=10, timeout=60):
for s in OpenSearchStore.search(self, query, max_results, timeout):
s.drm = SearchResult.DRM_UNLOCKED
s.formats = 'EPUB, PDF, MOBI'
yield s

View File

@ -0,0 +1,6 @@
from description import Description
from query import Query
from client import Client
from results import Results
Description, Query, Client, Results

View File

@ -0,0 +1,39 @@
from description import Description
from query import Query
from results import Results
class Client:
"""This is the class you'll probably want to be using. You simply
pass the constructor the url for the service description file and
issue a search and get back results as an iterable Results object.
The neat thing about a Results object is that it will seamlessly
handle fetching more results from the opensearch server when it can...
so you just need to iterate and can let the paging be taken care of
for you.
from opensearch import Client
client = Client(description_url)
results = client.search("computer")
for result in results:
print result.title
"""
def __init__(self, url, agent="python-opensearch <https://github.com/edsu/opensearch>"):
self.agent = agent
self.description = Description(url, self.agent)
def search(self, search_terms, page_size=25):
"""Perform a search and get back a results object
"""
url = self.description.get_best_template()
query = Query(url)
# set up initial values
query.searchTerms = search_terms
query.count = page_size
# run the results
return Results(query, agent=self.agent)

View File

@ -0,0 +1,127 @@
from urllib2 import urlopen, Request
from xml.dom.minidom import parse
from url import URL
class Description:
"""A class for representing OpenSearch Description files.
"""
def __init__(self, url="", agent=""):
"""The constructor which may pass an optional url to load from.
d = Description("http://www.example.com/description")
"""
self.agent = agent
if url:
self.load(url)
def load(self, url):
"""For loading up a description object from a url. Normally
you'll probably just want to pass a URL into the constructor.
"""
req = Request(url, headers={'User-Agent':self.agent})
self.dom = parse(urlopen(req))
# version 1.1 has repeating Url elements
self.urls = self._get_urls()
# this is version 1.0 specific
self.url = self._get_element_text('Url')
self.format = self._get_element_text('Format')
self.shortname = self._get_element_text('ShortName')
self.longname = self._get_element_text('LongName')
self.description = self._get_element_text('Description')
self.image = self._get_element_text('Image')
self.samplesearch = self._get_element_text('SampleSearch')
self.developer = self._get_element_text('Developer')
self.contact = self._get_element_text('Contact')
self.attribution = self._get_element_text('Attribution')
self.syndicationright = self._get_element_text('SyndicationRight')
tag_text = self._get_element_text('Tags')
if tag_text != None:
self.tags = tag_text.split(" ")
if self._get_element_text('AdultContent') == 'true':
self.adultcontent = True
else:
self.adultcontent = False
def get_url_by_type(self, type):
"""Walks available urls and returns them by type. Only
appropriate in opensearch v1.1 where there can be multiple
query targets. Returns none if no such type is found.
url = description.get_url_by_type('application/rss+xml')
"""
for url in self.urls:
if url.type == type:
return url
return None
def get_best_template(self):
"""OK, best is a value judgement, but so be it. You'll get
back either the atom, rss or first template available. This
method handles the main difference between opensearch v1.0 and v1.1
"""
# version 1.0
if self.url:
return self.url
# atom
if self.get_url_by_type('application/atom+xml'):
return self.get_url_by_type('application/atom+xml').template
# rss
if self.get_url_by_type('application/rss+xml'):
return self.get_url_by_type('application/rss+xml').template
# other possible rss type
if self.get_url_by_type('text/xml'):
return self.get_url_by_Type('text/xml').template
# otherwise just the first one
if len(self.urls) > 0:
return self.urls[0].template
# out of luck
return None
# these are internal methods for querying xml
def _get_element_text(self, tag):
elements = self._get_elements(tag)
if not elements:
return None
return self._get_text(elements[0].childNodes)
def _get_attribute_text(self, tag, attribute):
elements = self._get_elements(tag)
if not elements:
return ''
return elements[0].getAttribute('template')
def _get_elements(self, tag):
return self.dom.getElementsByTagName(tag)
def _get_text(self, nodes):
text = ''
for node in nodes:
if node.nodeType == node.TEXT_NODE:
text += node.data
return text.strip()
def _get_urls(self):
urls = []
for element in self._get_elements('Url'):
template = element.getAttribute('template')
type = element.getAttribute('type')
if template and type:
url = URL()
url.template = template
url.type = type
urls.append(url)
return urls

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
from urlparse import urlparse, urlunparse
from urllib import urlencode
from cgi import parse_qs
class Query:
"""Represents an opensearch query. Used internally by the Client to
construct an opensearch url to request. Really this class is just a
helper for substituting values into the macros in a format.
format = 'http://beta.indeed.com/opensearch?q={searchTerms}&start={startIndex}&limit={count}'
q = Query(format)
q.searchTerms('zx81')
q.startIndex = 1
q.count = 25
print q.to_url()
"""
standard_macros = ['searchTerms','count','startIndex','startPage',
'language', 'outputEncoding', 'inputEncoding']
def __init__(self, format):
"""Create a query object by passing it the url format obtained
from the opensearch Description.
"""
self.format = format
# unpack the url to a tuple
self.url_parts = urlparse(format)
# unpack the query string to a dictionary
self.query_string = parse_qs(self.url_parts[4])
# look for standard macros and create a mapping of the
# opensearch names to the service specific ones
# so q={searchTerms} will result in a mapping between searchTerms and q
self.macro_map = {}
for key,values in self.query_string.items():
# TODO eventually optional/required params should be
# distinguished somehow (the ones with/without trailing ?
macro = values[0].replace('{','').replace('}','').replace('?','')
if macro in Query.standard_macros:
self.macro_map[macro] = key
def url(self):
# copy the original query string
query_string = dict(self.query_string)
# iterate through macros and set the position in the querystring
for macro, name in self.macro_map.items():
if hasattr(self, macro):
# set the name/value pair
query_string[name] = [getattr(self, macro)]
else:
# remove the name/value pair
del(query_string[name])
# copy the url parts and substitute in our new query string
url_parts = list(self.url_parts)
url_parts[4] = urlencode(query_string, 1)
# recompose and return url
return urlunparse(tuple(url_parts))
def has_macro(self, macro):
return self.macro_map.has_key(macro)

View File

@ -0,0 +1,131 @@
class Results(object):
def __init__(self, query, agent=None):
self.agent = agent
self._fetch(query)
self._iter = 0
def __iter__(self):
self._iter = 0
return self
def __len__(self):
return self.totalResults
def next(self):
# just keep going like the energizer bunny
while True:
# return any item we haven't returned
if self._iter < len(self.items):
self._iter += 1
return self.items[self._iter-1]
# if there appears to be more to fetch
if \
self.totalResults != 0 \
and self.totalResults > self.startIndex + self.itemsPerPage - 1:
# get the next query
next_query = self._get_next_query()
# if we got one executed it and go back to the beginning
if next_query:
self._fetch(next_query)
# very important to reset this counter
# or else the return will fail
self._iter = 0
else:
raise StopIteration
def _fetch(self, query):
import osfeedparser
feed = osfeedparser.opensearch_parse(query.url(), agent=self.agent)
self.feed = feed
# general channel stuff
channel = feed['feed']
self.title = _pick(channel,'title')
self.link = _pick(channel,'link')
self.description = _pick(channel,'description')
self.language = _pick(channel,'language')
self.copyright = _pick(channel,'copyright')
# get back opensearch specific values
self.totalResults = _pick(channel,'opensearch_totalresults',0)
self.startIndex = _pick(channel,'opensearch_startindex',1)
self.itemsPerPage = _pick(channel,'opensearch_itemsperpage',0)
# alias items from the feed to our results object
self.items = feed['items']
# set default values if necessary
if self.startIndex == 0:
self.startIndex = 1
if self.itemsPerPage == 0 and len(self.items) > 0:
self.itemsPerPage = len(self.items)
# store away query for calculating next results
# if necessary
self.last_query = query
def _get_next_query(self):
# update our query to get the next set of records
query = self.last_query
# use start page if the query supports it
if query.has_macro('startPage'):
# if the query already defined the startPage
# we just need to increment it
if hasattr(query, 'startPage'):
query.startPage += 1
# to issue the first query startPage might not have
# been specified, so set it to 2
else:
query.startPage = 2
return query
# otherwise the query should support startIndex
elif query.has_macro('startIndex'):
# if startIndex was used before we just add the
# items per page to it to get the next set
if hasattr(query, 'startIndex'):
query.startIndex += self.itemsPerPage
# to issue the first query the startIndex may have
# been left blank in that case we assume it to be
# the item just after the last one on this page
else:
query.startIndex = self.itemsPerPage + 1
return query
# doesn't look like there is another stage to this query
return None
# helper for pulling values out of a dictionary if they're there
# and returning a default value if they're not
def _pick(d,key,default=None):
# get the value out
value = d.get(key)
# if it wasn't there return the default
if value == None:
return default
# if they want an int try to convert to an int
# and return default if it fails
if type(default) == int:
try:
return int(d[key])
except:
return default
# otherwise we're good to return the value
return value

View File

@ -0,0 +1,8 @@
class URL:
"""Class for representing a URL in an opensearch v1.1 query"""
def __init__(self, type='', template='', method='GET'):
self.type = type
self.template = template
self.method = 'GET'
self.params = []