mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
0e9f711c5d
46
recipes/bild_de.recipe
Normal file
46
recipes/bild_de.recipe
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
title = u'Bild.de'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 50
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
|
||||
# get cover from myspace
|
||||
cover_url = 'http://a3.l3-images.myspacecdn.com/images02/56/0232f842170b4d349779f8379c27e073/l.jpg'
|
||||
|
||||
# set what to fetch on the site
|
||||
remove_tags_before = dict(name = 'h2', attrs={'id':'cover'})
|
||||
remove_tags_after = dict(name ='div', attrs={'class':'back'})
|
||||
|
||||
# thanx to kiklop74 for code (see sticky thread -> Recipes - Re-usable code)
|
||||
# this one removes a lot of direct-link's
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
# remove the ad's
|
||||
filter_regexps = [r'.\.smartadserver\.com']
|
||||
def skip_ad_pages(self, soup):
|
||||
return None
|
||||
|
||||
#get the real url behind .feedsportal.com and fetch the artikels
|
||||
def get_article_url(self, article):
|
||||
return article.get('id', article.get('guid', None))
|
||||
|
||||
#list of the rss source from www.bild.de
|
||||
feeds = [(u'Überblick', u'http://rss.bild.de/bild.xml'),
|
||||
(u'News', u'http://rss.bild.de/bild-news.xml'),
|
||||
(u'Politik', u'http://rss.bild.de/bild-politik.xml'),
|
||||
(u'Unterhaltung', u'http://rss.bild.de/bild-unterhaltung.xml'),
|
||||
(u'Sport', u'http://rss.bild.de/bild-sport.xml'),
|
||||
(u'Lifestyle', u'http://rss.bild.de/bild-lifestyle.xml'),
|
||||
(u'Ratgeber', u'http://rss.bild.de/bild-ratgeber.xml')
|
||||
]
|
33
recipes/borse_online.recipe
Normal file
33
recipes/borse_online.recipe
Normal file
@ -0,0 +1,33 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'Börse-online'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.dpv.de/images/1995/source.gif'
|
||||
masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg'
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
remove_tags_bevor = [dict(name='h3')]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
|
||||
remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}),
|
||||
dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']),
|
||||
dict(name=['h2', 'Gesamtranking', 'h3',''])]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('.html#nv=rss', '.html?mode=print')
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')]
|
||||
|
61
recipes/capital_de.recipe
Normal file
61
recipes/capital_de.recipe
Normal file
@ -0,0 +1,61 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
|
||||
title = u'Capital.de'
|
||||
language = 'de'
|
||||
__author__ = 'schuster'
|
||||
oldest_article =7
|
||||
max_articles_per_feed = 35
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
masthead_url = 'http://www.wirtschaftsmedien-shop.de/media/stores/wirtschaftsmedien/capital/teaser_large_abo.jpg'
|
||||
cover_url = 'http://d1kb9jvg6ylufe.cloudfront.net/WebsiteCMS/de/unternehmen/linktipps/mainColumn/08/image/DE_Capital_bis20mm_SW.jpg'
|
||||
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
def print_version(self, url):
|
||||
return url.replace ('nv=rss#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', 'mode=print')
|
||||
remove_tags_bevor = [dict(name='td', attrs={'class':'textcell'})]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'artikelsplit'})]
|
||||
|
||||
feeds = [ (u'Wirtschaftsmagazin', u'http://www.capital.de/rss/'),
|
||||
(u'Unternehmen', u'http://www.capital.de/rss/unternehmen'),
|
||||
(u'Finanz & Geldanlage', u'http://www.capital.de/rss/finanzen/geldanlage')]
|
||||
|
||||
def append_page(self, soup, appendtag, position):
|
||||
pager = soup.find('div',attrs={'class':'artikelsplit'})
|
||||
if pager:
|
||||
nexturl = self.INDEX + pager.a['href']
|
||||
soup2 = self.index_to_soup(nexturl)
|
||||
texttag = soup2.find('div', attrs={'class':'printable'})
|
||||
for it in texttag.findAll(style=True):
|
||||
del it['style']
|
||||
newpos = len(texttag.contents)
|
||||
self.append_page(soup2,texttag,newpos)
|
||||
texttag.extract()
|
||||
appendtag.insert(position,texttag)
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('div', attrs={'class':'artikelsplit'}):
|
||||
item.extract()
|
||||
self.append_page(soup, soup.body, 3)
|
||||
pager = soup.find('div',attrs={'class':'artikelsplit'})
|
||||
if pager:
|
||||
pager.extract()
|
||||
return self.adeify_images(soup)
|
||||
|
||||
|
||||
|
||||
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'artikelsplit']}),
|
||||
dict(id=['topNav', 'mainNav', 'subNav', 'socialmedia', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere']),
|
||||
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
|
||||
dict(rel=['canonical'])]
|
||||
|
34
recipes/cosmopolitan_de.recipe
Normal file
34
recipes/cosmopolitan_de.recipe
Normal file
@ -0,0 +1,34 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1305567197(BasicNewsRecipe):
|
||||
title = u'Cosmopolitan.de'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 7
|
||||
language = 'de'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.cosmopolitan.com/cm/shared/site_images/print_this/cosmopolitan_logo.gif'
|
||||
remove_tags_before = dict(name = 'h1', attrs={'class':'artikel'})
|
||||
remove_tags_after = dict(name ='div', attrs={'class':'morePages'})
|
||||
extra_css = '''
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||
'''
|
||||
remove_tags = [ dict(id='strong'),
|
||||
dict(title='strong'),
|
||||
dict(name='span'),
|
||||
dict(name='li', attrs={'class':'large'}),
|
||||
dict(name='ul', attrs={'class':'articleImagesPortrait clearfix'}),
|
||||
dict(name='p', attrs={'class':'external'}),
|
||||
dict(name='a', attrs={'target':'_blank'}),]
|
||||
feeds = [ (u'Komplett', u'http://www.cosmopolitan.de/rss/allgemein.xml'),
|
||||
(u'Mode', u'http://www.cosmopolitan.de/rss/mode.xml'),
|
||||
(u'Beauty', u'http://www.cosmopolitan.de/rss/beauty.xml'),
|
||||
(u'Liebe&Sex', u'http://www.cosmopolitan.de/rss/liebe.xml'),
|
||||
(u'Psychologie', u'http://www.cosmopolitan.de/rss/psychologie.xml'),
|
||||
(u'Job&Karriere', u'http://www.cosmopolitan.de/rss/job.xml'),
|
||||
(u'Lifestyle', u'http://www.cosmopolitan.de/rss/lifestyle.xml'),
|
||||
(u'Shopping', u'http://www.cosmopolitan.de/rss/shopping.xml'),
|
||||
(u'Bildergalerien', u'http://www.cosmopolitan.de/rss/bildgalerien.xml')]
|
@ -37,7 +37,7 @@ class DN_se(BasicNewsRecipe):
|
||||
,(u'Kultur' , u'http://www.dn.se/kultur-rss' )
|
||||
]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'article-content'})]
|
||||
remove_tags_before = dict(name='h1')
|
||||
remove_tags_after = dict(name='div',attrs={'id':'byline'})
|
||||
remove_tags = [
|
||||
|
@ -1,19 +1,21 @@
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
||||
title = u'DvhN'
|
||||
oldest_article = 1
|
||||
__author__ = 'Reijndert'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 200
|
||||
|
||||
__author__ = 'Reijndert'
|
||||
no_stylesheets = True
|
||||
cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif'
|
||||
cover_url = 'http://members.home.nl/apm.de.haas/calibre/DvhN.jpg'
|
||||
language = 'nl'
|
||||
country = 'NL'
|
||||
version = 1
|
||||
publisher = u'Dagblad van het Noorden'
|
||||
category = u'Nieuws'
|
||||
description = u'Nieuws uit Noord Nederland'
|
||||
timefmt = ' %Y-%m-%d (%a)'
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'})
|
||||
@ -21,11 +23,26 @@ class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','iframe','base'])
|
||||
,dict(name='span',attrs={'class':'copyright'})
|
||||
dict(name='span',attrs={'class':'location'})
|
||||
]
|
||||
|
||||
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')]
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<a.*?>'), lambda h1: '')
|
||||
,(re.compile(r'</a>'), lambda h2: '')
|
||||
,(re.compile(r'Word vriend van Dagblad van het Noorden op Facebook'), lambda h3: '')
|
||||
,(re.compile(r'Volg Dagblad van het Noorden op Twitter'), lambda h3: '')
|
||||
]
|
||||
|
||||
|
||||
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss')
|
||||
, (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss')
|
||||
, (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss')
|
||||
, (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss')
|
||||
, (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss')
|
||||
, (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss')
|
||||
, (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss')
|
||||
, (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
body {font-family: verdana, arial, helvetica, geneva, sans-serif;}
|
||||
|
@ -20,7 +20,7 @@ class Economist(BasicNewsRecipe):
|
||||
INDEX = 'http://www.economist.com/printedition'
|
||||
description = ('Global news and current affairs from a European'
|
||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||
|
||||
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||
oldest_article = 7.0
|
||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||
remove_tags = [
|
||||
|
74
recipes/express_de.recipe
Normal file
74
recipes/express_de.recipe
Normal file
@ -0,0 +1,74 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'Express.de'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 50
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
extra_css = '''
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||
|
||||
'''
|
||||
remove_javascript = True
|
||||
remove_tags_befor = [dict(name='div', attrs={'class':'Datum'})]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'MoreNews'})]
|
||||
|
||||
remove_tags = [dict(id='kalaydo'),
|
||||
dict(id='Header'),
|
||||
dict(id='Searchline'),
|
||||
dict(id='MainNav'),
|
||||
dict(id='Logo'),
|
||||
dict(id='MainLinkSpacer'),
|
||||
dict(id='MainLinks'),
|
||||
dict(title='Diese Seite Bookmarken'),
|
||||
|
||||
dict(name='span'),
|
||||
dict(name='div', attrs={'class':'spacer_leftneu'}),
|
||||
dict(name='div', attrs={'class':'button kalaydologo'}),
|
||||
dict(name='div', attrs={'class':'button stellenneu'}),
|
||||
dict(name='div', attrs={'class':'button autoneu'}),
|
||||
dict(name='div', attrs={'class':'button immobilienneu'}),
|
||||
dict(name='div', attrs={'class':'button kleinanzeigen'}),
|
||||
dict(name='div', attrs={'class':'button tiereneu'}),
|
||||
dict(name='div', attrs={'class':'button ferienwohnungen'}),
|
||||
dict(name='div', attrs={'class':'button inserierenneu'}),
|
||||
dict(name='div', attrs={'class':'spacer_rightneu'}),
|
||||
dict(name='div', attrs={'class':'spacer_rightcorner'}),
|
||||
dict(name='div', attrs={'class':'HeaderMetaNav'}),
|
||||
dict(name='div', attrs={'class':'HeaderSearchOption'}),
|
||||
dict(name='div', attrs={'class':'HeaderSearch'}),
|
||||
dict(name='div', attrs={'class':'sbutton'}),
|
||||
dict(name='div', attrs={'class':'active'}),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
feeds = [(u'Top-Themen', u'http://www.express.de/home/-/2126/2126/-/view/asFeed/-/index.xml'),
|
||||
(u'Regional - Köln', u'http://www.express.de/regional/koeln/-/2856/2856/-/view/asFeed/-/index.xml'),
|
||||
(u'Regional - Bonn', u'http://www.express.de/regional/bonn/-/2860/2860/-/view/asFeed/-/index.xml'),
|
||||
(u'Regional - Düsseldorf', u'http://www.express.de/regional/duesseldorf/-/2858/2858/-/view/asFeed/-/index.xml'),
|
||||
(u'Regional - Region', u'http://www.express.de/regional/-/2178/2178/-/view/asFeed/-/index.xml'),
|
||||
(u'Sport-News', u'http://www.express.de/sport/-/2176/2176/-/view/asFeed/-/index.xml'),
|
||||
(u'Fussball-News', u'http://www.express.de/sport/fussball/-/3186/3186/-/view/asFeed/-/index.xml'),
|
||||
(u'1.FC Köln News', u'http://www.express.de/sport/fussball/fc-koeln/-/3192/3192/-/view/asFeed/-/index.xml'),
|
||||
(u'Alemannia Aachen News', u'http://www.express.de/sport/fussball/alemannia/-/3290/3290/-/view/asFeed/-/index.xml'),
|
||||
(u'Borussia M~Gladbach', u'http://www.express.de/sport/fussball/gladbach/-/3286/3286/-/view/asFeed/-/index.xml'),
|
||||
(u'Fortuna D~Dorf', u'http://www.express.de/sport/fussball/fortuna/-/3292/3292/-/view/asFeed/-/index.xml'),
|
||||
(u'Basketball News', u'http://www.express.de/sport/basketball/-/3190/3190/-/view/asFeed/-/index.xml'),
|
||||
(u'Big Brother', u'http://www.express.de/news/promi-show/big-brother/-/2402/2402/-/view/asFeed/-/index.xml'),
|
||||
|
||||
|
||||
|
||||
]
|
@ -1,51 +1,38 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Kovid Goyal <kovid at kovidgoyal.net>, Darko Miletic <darko at gmail.com>'
|
||||
'''
|
||||
Profile to download FAZ.net
|
||||
'''
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class FazNet(BasicNewsRecipe):
|
||||
title = 'FAZ NET'
|
||||
__author__ = 'Kovid Goyal, Darko Miletic'
|
||||
title = u'Faz.net'
|
||||
__author__ = 'schuster'
|
||||
remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}),
|
||||
dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
|
||||
dict(name=['script', 'noscript', 'style'])]
|
||||
oldest_article = 2
|
||||
description = 'Frankfurter Allgemeine Zeitung'
|
||||
publisher = 'FAZ Electronic Media GmbH'
|
||||
category = 'news, politics, Germany'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
|
||||
max_articles_per_feed = 30
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'Article'})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','embed','base'])
|
||||
,dict(name='div', attrs={'class':['LinkBoxModulSmall','ModulVerlagsInfo']})
|
||||
]
|
||||
|
||||
|
||||
feeds = [ ('FAZ.NET', 'http://www.faz.net/s/Rub/Tpl~Epartner~SRss_.xml') ]
|
||||
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
|
||||
|
||||
def print_version(self, url):
|
||||
article, sep, rest = url.partition('?')
|
||||
return article.replace('.html', '~Afor~Eprint.html')
|
||||
return url.replace('.html', '~Afor~Eprint.html')
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Wirtschaft', u'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Feuilleton', u'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Sport', u'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Gesellschaft', u'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Finanzen', u'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Wissen', u'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Reise', u'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Technik & Motor', u'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Beruf & Chance', u'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Kunstmarkt', u'http://www.faz.net/s/RubBC09F7BF72A2405A96718ECBFB68FBFE/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Immobilien ', u'http://www.faz.net/s/RubFED172A9E10F46B3A5F01B02098C0C8D/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Rhein-Main Zeitung', u'http://www.faz.net/s/RubABE881A6669742C2A5EBCB5D50D7EBEE/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Atomdebatte ', u'http://www.faz.net/s/Rub469C43057F8C437CACC2DE9ED41B7950/Tpl~Epartner~SRss_.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
del soup.body['onload']
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
64
recipes/financialsense.recipe
Normal file
64
recipes/financialsense.recipe
Normal file
@ -0,0 +1,64 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.financialsense.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class FinancialSense(BasicNewsRecipe):
|
||||
title = 'Financial Sense'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Uncommon News & Views for the Wise Investor'
|
||||
publisher = 'Financial Sense'
|
||||
category = 'news, finances, politics, USA'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'en'
|
||||
remove_empty_feeds = True
|
||||
publication_type = 'newsportal'
|
||||
masthead_url = 'http://www.financialsense.com/sites/default/files/logo.jpg'
|
||||
extra_css = """
|
||||
body{font-family: Arial,"Helvetica Neue",Helvetica,sans-serif }
|
||||
img{margin-bottom: 0.4em; display:block}
|
||||
h2{color: gray}
|
||||
.name{margin-right: 5em}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags =[dict(name=['meta','link','base','object','embed','iframe'])]
|
||||
remove_tags_after=dict(attrs={'class':'vcard'})
|
||||
keep_only_tags =[dict(attrs={'class':['title','post-meta','content','item-title','vcard']})]
|
||||
remove_attributes=['lang','type']
|
||||
|
||||
|
||||
feeds = [(u'Articles', u'http://feeds.feedburner.com/fso')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('a'):
|
||||
limg = item.find('img')
|
||||
if item.string is not None:
|
||||
str = item.string
|
||||
item.replaceWith(str)
|
||||
else:
|
||||
if limg:
|
||||
item.name = 'div'
|
||||
item.attrs = []
|
||||
else:
|
||||
str = self.tag_to_string(item)
|
||||
item.replaceWith(str)
|
||||
for item in soup.findAll('img'):
|
||||
if not item.has_key('alt'):
|
||||
item['alt'] = 'image'
|
||||
return soup
|
38
recipes/glamour.recipe
Normal file
38
recipes/glamour.recipe
Normal file
@ -0,0 +1,38 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||
title = u'Glamour (US)'
|
||||
oldest_article = 21
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'en'
|
||||
remove_javascript = True
|
||||
__author__ = 'Anonymous'
|
||||
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?printable=true'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
feeds = [ (u'All Fashion', u'http://feeds.glamour.com/glamour/all_fashion'),
|
||||
(u'All Beauty', u'http://feeds.glamour.com/glamour/all_beauty'),
|
||||
(u'All Sex, Love & Life', u'http://feeds.glamour.com/glamour/sex_love_life'),
|
||||
(u'All Health & Fitness', u'http://feeds.glamour.com/glamour/health_fitness'),
|
||||
(u'Shopping', u'http://feeds.glamour.com/glamour/shopping'),
|
||||
(u'Slaves to Fashion blog', u'http://feeds.glamour.com/glamour/slavestofashion'),
|
||||
(u'The Girls in the Beauty Department', u'http://feeds.glamour.com/glamour/thegirlsinthebeautydepartment'),
|
||||
(u'Smitten blog', u'http://feeds.glamour.com/glamour/smitten'),
|
||||
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
|
||||
(u'Single-ish blog', u'http://feeds.glamour.com/glamour/glamoursingle-ish'),
|
||||
(u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'),
|
||||
(u'Vitamin G blog', u'http://feeds.glamour.com/glamour/vitamin-g'),
|
||||
(u'Margarita Shapes Up blog', u'http://feeds.glamour.com/glamour/margaritashapesup'),
|
||||
(u'Little Miss Fortune blog', u'http://feeds.glamour.com/glamour/little-miss-fortune'),
|
||||
]
|
@ -1,50 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class golem_ger(BasicNewsRecipe):
|
||||
title = u'Golem.de'
|
||||
language = 'de'
|
||||
__author__ = 'Kovid Goyal'
|
||||
__author__ = 'schuster'
|
||||
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = 'de'
|
||||
lang = 'de-DE'
|
||||
max_articles_per_feed = 10
|
||||
no_stylesheets = True
|
||||
encoding = 'iso-8859-1'
|
||||
recursions = 1
|
||||
match_regexps = [r'http://www.golem.de/.*.html']
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
cover_url = 'http://www.e-energy.de/images/logo_golem.jpg'
|
||||
masthead_url = 'http://www.golem.de/staticrl/images/logo.png'
|
||||
extra_css = '''
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;}
|
||||
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='h1', attrs={'class':'artikelhead'}),
|
||||
dict(name='p', attrs={'class':'teaser'}),
|
||||
dict(name='div', attrs={'class':'artikeltext'}),
|
||||
dict(name='h2', attrs={'id':'artikelhead'}),
|
||||
]
|
||||
'''
|
||||
remove_javascript = True
|
||||
remove_tags_befor = [dict(name='header', attrs={'class':'cluster-header'})]
|
||||
remove_tags_after = [dict(name='p', attrs={'class':'meta'})]
|
||||
remove_tags = [dict(rel='nofollow'),
|
||||
dict(name='header', attrs={'id':'header'}),
|
||||
dict(name='div', attrs={'class':'dh1'}),
|
||||
dict(name='label', attrs={'class':'implied'}),
|
||||
dict(name='section', attrs={'id':'comments'}),
|
||||
dict(name='li', attrs={'class':'gg_prebackcounterItem'}),
|
||||
dict(name='li', attrs={'class':'gg_prebackcounterItem gg_embeddedIndexCounter'}),
|
||||
dict(name='img', attrs={'class':'gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer'}),
|
||||
dict(name='div', attrs={'target':'_blank'})
|
||||
]
|
||||
|
||||
def get_browser(self, *args, **kwargs):
|
||||
from calibre import browser
|
||||
kwargs['user_agent'] = 'mozilla'
|
||||
return browser(*args, **kwargs)
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('id', article.get('guid', None))
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':['similarContent','topContentWrapper','storycarousel','aboveFootPromo','comments','toolbar','breadcrumbs','commentlink','sidebar','rightColumn']}),
|
||||
dict(name='div', attrs={'class':['gg_embeddedSubText','gg_embeddedIndex gg_solid','gg_toOldGallery','golemGallery']}),
|
||||
dict(name='img', attrs={'class':['gg_embedded','gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer']}),
|
||||
dict(name='td', attrs={'class':['xsmall']}),
|
||||
]
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
|
||||
# remove_tags_after = [
|
||||
# dict(name='div', attrs={'id':['contentad2']})
|
||||
# ]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
|
||||
(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
|
||||
feeds = [(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'),
|
||||
(u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'),
|
||||
(u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'),
|
||||
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
|
||||
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=ATOM1.0'),
|
||||
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'),
|
||||
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=ATOM1.0'),
|
||||
(u'Handy', u'http://rss.golem.de/rss.php?tp=handy&feed=RSS2.0'),
|
||||
(u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS2.0'),
|
||||
(u'Mobile', u'http://rss.golem.de/rss.php?tp=mc&feed=RSS2.0'),
|
||||
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
|
||||
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
|
||||
(u'Security', u'http://rss.golem.de/rss.php?tp=sec&feed=RSS2.0'),
|
||||
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
|
||||
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
|
||||
(u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'),
|
||||
@ -53,31 +63,8 @@ class golem_ger(BasicNewsRecipe):
|
||||
(u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'),
|
||||
(u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'),
|
||||
(u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'),
|
||||
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0'),
|
||||
(u'Unternehmen/Maerkte', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0')
|
||||
]
|
||||
(u'Wirtschaft', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0'),
|
||||
(u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0')
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'),
|
||||
(u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=feed=RSS2.0'),
|
||||
(u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'),
|
||||
(u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'),
|
||||
(u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'),
|
||||
(u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'),
|
||||
]
|
||||
|
||||
|
||||
extra_css = '''
|
||||
h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:30px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;margin-bottom:2 em;}
|
||||
h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:22px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; }
|
||||
h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:x-small; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal; line-height:5px;}
|
||||
h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; }
|
||||
h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;}
|
||||
.teaser {font-style:italic;font-size:12pt;margin-bottom:15pt;}
|
||||
.xsmall{font-style:italic;font-size:x-small;}
|
||||
.td{font-style:italic;font-size:x-small;}
|
||||
img {align:left;}
|
||||
'''
|
||||
|
31
recipes/good_house_keeping.recipe
Normal file
31
recipes/good_house_keeping.recipe
Normal file
@ -0,0 +1,31 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||
title = u'Good House Keeping'
|
||||
language = 'en'
|
||||
__author__ = 'Anonymous'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
|
||||
def print_version(self,url):
|
||||
segments = url.split('/')
|
||||
printURL = '/'.join(segments[0:3]) + '/print-this/' + '/'.join(segments[4:])
|
||||
return printURL
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
feeds = [ (u'Recipes & Entertaining', u'http://www.goodhousekeeping.com/food/food-rss/?src=rss'),
|
||||
(u'Home & House', u'http://www.goodhousekeeping.com/home/home-rss/?src=rss'),
|
||||
(u'Diet & Health', u'http://www.goodhousekeeping.com/health/health-rss/?src=rss'),
|
||||
(u'Beauty & Style', u'http://www.goodhousekeeping.com/beauty/beauty-rss/?src=rss'),
|
||||
(u'Family & Pets', u'http://www.goodhousekeeping.com/family/family-rss/?src=rss'),
|
||||
(u'Saving Money', u'http://www.goodhousekeeping.com/money/money-rss/?src=rss'),
|
||||
]
|
32
recipes/good_to_know.recipe
Normal file
32
recipes/good_to_know.recipe
Normal file
@ -0,0 +1,32 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1305547242(BasicNewsRecipe):
|
||||
title = u'Good to Know (uk)'
|
||||
oldest_article = 14
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
__author__ = 'Anonymous'
|
||||
language = 'en_GB'
|
||||
remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '/print/1'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
feeds = [ (u'Family Conception Advice', u'http://www.goodtoknow.co.uk/feeds/family.rss'),
|
||||
(u'Family Health Advice', u'http://www.goodtoknow.co.uk/feeds/health.rss'),
|
||||
(u'Diet Advice', u'http://www.goodtoknow.co.uk/feeds/diet.rss'),
|
||||
(u'Food Advice', u'http://www.goodtoknow.co.uk/feeds/food.rss'),
|
||||
(u'Sex Advice', u'http://www.goodtoknow.co.uk/feeds/sex.rss'),
|
||||
(u'Easy Exercise', u'http://www.goodtoknow.co.uk/feeds/easyexercise.rss'),
|
||||
(u'Recipes', u'http://www.goodtoknow.co.uk/feeds/recipes.rss'),
|
||||
(u'Food Quick-tips', u'http://www.goodtoknow.co.uk/feeds/foodquicktips.rss'),
|
||||
]
|
BIN
recipes/icons/financialsense.png
Normal file
BIN
recipes/icons/financialsense.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 702 B |
BIN
recipes/icons/iprofesional.png
Normal file
BIN
recipes/icons/iprofesional.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
recipes/icons/osnews_pl.png
Normal file
BIN
recipes/icons/osnews_pl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1006 B |
BIN
recipes/icons/rmf24_opinie.png
Normal file
BIN
recipes/icons/rmf24_opinie.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 722 B |
BIN
recipes/icons/swiatkindle.png
Normal file
BIN
recipes/icons/swiatkindle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 425 B |
32
recipes/impulse_de.recipe
Normal file
32
recipes/impulse_de.recipe
Normal file
@ -0,0 +1,32 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1305470859(BasicNewsRecipe):
|
||||
title = u'Impulse.de'
|
||||
language = 'de'
|
||||
__author__ = 'schuster'
|
||||
oldest_article =14
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
cover_url = 'http://www.bvk.de/files/image/bilder/Logo%20Impulse.jpg'
|
||||
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
def print_version(self, url):
|
||||
return url.replace ('#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', '?mode=print')
|
||||
remove_tags_bevor = [dict(name='h1', attrs={'class':'h2'})]
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
|
||||
|
||||
feeds = [ (u'impulstest', u'http://www.impulse.de/rss/')]
|
||||
|
||||
|
||||
remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'kasten_artikel']}),
|
||||
dict(id=['metaNav', 'impKopf', 'impTopNav', 'impSubNav', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere', 'socialmedia', 'rating_open']),
|
||||
dict(span=['ratingtext', 'Gesamtranking', 'h3','']),
|
||||
dict(rel=['canonical'])]
|
||||
|
79
recipes/iprofesional.recipe
Normal file
79
recipes/iprofesional.recipe
Normal file
@ -0,0 +1,79 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.iprofesional.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class iProfesional(BasicNewsRecipe):
|
||||
title = 'iProfesional.com'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Las ultimas noticias sobre profesionales'
|
||||
publisher = 'Emprendimientos Corporativos S.A.'
|
||||
category = 'news, IT, impuestos, negocios, politics, Argentina'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'es_AR'
|
||||
remove_empty_feeds = True
|
||||
publication_type = 'nesportal'
|
||||
masthead_url = 'http://www.iprofesional.com/img/logo-iprofesional.png'
|
||||
extra_css = """
|
||||
body{font-family: Arial,Helvetica,sans-serif }
|
||||
img{margin-bottom: 0.4em; display:block}
|
||||
.titulo-interior{font-family: Georgia,"Times New Roman",Times,serif}
|
||||
.autor-nota{font-size: small; font-weight: bold; font-style: italic; color: gray}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(attrs={'class':['fecha','interior-nota']})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['meta','link','base','embed','object','iframe'])
|
||||
,dict(attrs={'class':['menu-imprimir','guardarNota','IN-widget','fin','permalink']})
|
||||
]
|
||||
remove_attributes=['lang','xmlns:og','xmlns:fb']
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Ultimas noticias' , u'http://feeds.feedburner.com/iprofesional-principales-noticias')
|
||||
,(u'Finanzas' , u'http://feeds.feedburner.com/iprofesional-finanzas' )
|
||||
,(u'Impuestos' , u'http://feeds.feedburner.com/iprofesional-impuestos' )
|
||||
,(u'Negocios' , u'http://feeds.feedburner.com/iprofesional-economia' )
|
||||
,(u'Comercio Exterior' , u'http://feeds.feedburner.com/iprofesional-comercio-exterior' )
|
||||
,(u'Tecnologia' , u'http://feeds.feedburner.com/iprofesional-tecnologia' )
|
||||
,(u'Management' , u'http://feeds.feedburner.com/iprofesional-managment' )
|
||||
,(u'Marketing' , u'http://feeds.feedburner.com/iprofesional-marketing' )
|
||||
,(u'Legales' , u'http://feeds.feedburner.com/iprofesional-legales' )
|
||||
,(u'Autos' , u'http://feeds.feedburner.com/iprofesional-autos' )
|
||||
,(u'Vinos' , u'http://feeds.feedburner.com/iprofesional-vinos-bodegas' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('a'):
|
||||
limg = item.find('img')
|
||||
if item.string is not None:
|
||||
str = item.string
|
||||
item.replaceWith(str)
|
||||
else:
|
||||
if limg:
|
||||
item.name = 'div'
|
||||
item.attrs = []
|
||||
else:
|
||||
str = self.tag_to_string(item)
|
||||
item.replaceWith(str)
|
||||
for item in soup.findAll('img'):
|
||||
if not item.has_key('alt'):
|
||||
item['alt'] = 'image'
|
||||
return soup
|
22
recipes/max_planck.recipe
Normal file
22
recipes/max_planck.recipe
Normal file
@ -0,0 +1,22 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'Max-Planck-Inst.'
|
||||
__author__ = 'schuster'
|
||||
remove_tags = [dict(attrs={'class':['clearfix', 'lens', 'col2_box_list', 'col2_box_teaser group_ext no_print', 'dotted_line', 'col2_box_teaser', 'box_image small', 'bold', 'col2_box_teaser no_print', 'print_kontakt']}),
|
||||
dict(id=['ie_clearing', 'col2', 'col2_content']),
|
||||
dict(name=['script', 'noscript', 'style'])]
|
||||
oldest_article = 30
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
|
||||
def print_version(self, url):
|
||||
split_url = url.split("/")
|
||||
print_url = 'http://www.mpg.de/print/' + split_url[3]
|
||||
return print_url
|
||||
|
||||
feeds = [(u'Forschung', u'http://www.mpg.de/de/forschung.rss')]
|
||||
|
10
recipes/mens_health.recipe
Normal file
10
recipes/mens_health.recipe
Normal file
@ -0,0 +1,10 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1305636254(BasicNewsRecipe):
|
||||
title = u'Mens Health (US)'
|
||||
language = 'en'
|
||||
__author__ = 'Anonymous'
|
||||
oldest_article = 14
|
||||
max_articles_per_feed = 100
|
||||
|
||||
feeds = [(u'News', u'http://blogs.menshealth.com/health-headlines/feed')]
|
@ -11,6 +11,20 @@ class Newsweek(BasicNewsRecipe):
|
||||
|
||||
BASE_URL = 'http://www.newsweek.com'
|
||||
|
||||
topics = {
|
||||
'Culture' : '/tag/culture.html',
|
||||
'Business' : '/tag/business.html',
|
||||
'Society' : '/tag/society.html',
|
||||
'Science' : '/tag/science.html',
|
||||
'Education' : '/tag/education.html',
|
||||
'Politics' : '/tag/politics.html',
|
||||
'Health' : '/tag/health.html',
|
||||
'World' : '/tag/world.html',
|
||||
'Nation' : '/tag/nation.html',
|
||||
'Technology' : '/tag/technology.html',
|
||||
'Game Changers' : '/tag/game-changers.html',
|
||||
}
|
||||
|
||||
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
||||
remove_tags = [dict(attrs={'data-dartad':True})]
|
||||
remove_attributes = ['property']
|
||||
@ -21,14 +35,10 @@ class Newsweek(BasicNewsRecipe):
|
||||
return soup
|
||||
|
||||
def newsweek_sections(self):
|
||||
return [
|
||||
('Nation', 'http://www.newsweek.com/tag/nation.html'),
|
||||
('Society', 'http://www.newsweek.com/tag/society.html'),
|
||||
('Culture', 'http://www.newsweek.com/tag/culture.html'),
|
||||
('World', 'http://www.newsweek.com/tag/world.html'),
|
||||
('Politics', 'http://www.newsweek.com/tag/politics.html'),
|
||||
('Business', 'http://www.newsweek.com/tag/business.html'),
|
||||
]
|
||||
for topic_name, topic_url in self.topics.iteritems():
|
||||
yield (topic_name,
|
||||
self.BASE_URL+topic_url)
|
||||
|
||||
|
||||
def newsweek_parse_section_page(self, soup):
|
||||
for article in soup.findAll('article', about=True,
|
||||
|
29
recipes/ngz.recipe
Normal file
29
recipes/ngz.recipe
Normal file
@ -0,0 +1,29 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'NGZ-online'
|
||||
__author__ = 'schuster'
|
||||
remove_tags_before = dict(id='bu')
|
||||
remove_tags_after = dict(id='noblock')
|
||||
remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix', 'liketext']}),
|
||||
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index', 'Verlinken', 'vorheriger', 'LESERKOMMENTARE', 'bei facebook', 'bei twitter', 'Schreiben Sie jetzt Ihre Meinung:', 'Thema', 'Ihr Beitrag', 'Ihr Name', 'Ich möchte über weitere Lesermeinungen zu diesem Artikel per E-Mail informiert werden.', 'banneroben', 'bannerrechts', 'inserieren', 'stellen', 'auto', 'immobilien', 'kleinanzeige', 'tiere', 'ferienwohnung', 'NGZ Card', 'Mediengruppe RP', 'Werben', 'Newsletter', 'Wetter', 'RSS', 'Abo', 'Anzeigen', 'Redaktion', 'Schulprojekte', 'Gast', 'Mein NGZ', 'Nachrichten', 'Sport', 'Wirtschaft', 'Stadt-Infos', 'Bilderserien', 'Bookmarken', 'del.icio.us', 'Mister Wong', 'YiGG', 'Webnews', 'Shortnews', 'Twitter', 'Newsider', 'Facebook', 'StudiVZ/MeinVZ', 'Versenden', 'Drucken']),
|
||||
dict(name=['script', 'noscript', 'style'])]
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.rhein-kreis-neuss-macht-sport.de/sport/includes/bilder/ngz_logo.jpg'
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?ot=de.circit.rpo.PopupPageLayout.ot'
|
||||
feeds = [
|
||||
(u'Grevenbroich', u'http://www.ngz-online.de/app/feed/rss/grevenbroich'),
|
||||
(u'Kreis Neuss', u'http://www.ngz-online.de/app/feed/rss/rheinkreisneuss'),
|
||||
(u'Dormagen', u'http://www.ngz-online.de/app/feed/rss/dormagen'),
|
||||
(u'J\xfcchen', u'http://www.ngz-online.de/app/feed/rss/juechen'),
|
||||
(u'Rommerskirchen', u'http://www.ngz-online.de/app/feed/rss/rommerskirchen')
|
||||
|
||||
]
|
||||
|
22
recipes/pro_physik.recipe
Normal file
22
recipes/pro_physik.recipe
Normal file
@ -0,0 +1,22 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'Pro Physik'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 4
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.pro-physik.de/Phy/images/site/prophysik_logo1.jpg'
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('leadArticle.do', 'print.do')
|
||||
|
||||
|
||||
feeds = [(u'Hightech', u'http://www.pro-physik.de/Phy/hightechfeed.xml'),
|
||||
(u'Forschung', u'http://www.pro-physik.de/Phy/forschungfeed.xml'),
|
||||
(u'Magazin', u'http://www.pro-physik.de/Phy/magazinfeed.xml')]
|
||||
|
28
recipes/spektrum.recipe
Normal file
28
recipes/spektrum.recipe
Normal file
@ -0,0 +1,28 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
title = u'Spektrum (der Wissenschaft)'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = 'de'
|
||||
cover_url = 'http://upload.wikimedia.org/wikipedia/de/3/3b/Spektrum_der_Wissenschaft_Logo.svg'
|
||||
|
||||
remove_tags = [dict(attrs={'class':['hauptnaviPkt gainlayout', 'hauptnaviButton', 'suchButton', 'suchbegriffKasten', 'loginButton', 'subnavigation', 'artikelInfoLeiste gainlayout', 'artikelTools', 'nurLetzteSeite', 'link', 'boxUnterArtikel', 'leserbriefeBlock', 'boxTitel', 'boxInhalt', 'sehrklein', 'boxabstand', 'werbeboxinhalt', 'rbabstand', 'bildlinks', 'rechtebox', 'denkmalbox', 'denkmalfrage']}),
|
||||
dict(id=['pflip', 'verlagsleiste', 'bereich', 'bannerVertikal', 'headerLogoLink', 'kopf', 'topNavi', 'headerSchnellsuche', 'headerSchnellsucheWarten', 'navigation', 'navigationL', 'navigationR', 'inhalt', 'rechtespalte', 'sdwboxenshop', 'shopboxen', 'fuss']),
|
||||
dict(name=['naservice'])]
|
||||
|
||||
def print_version(self,url):
|
||||
newurl = url.replace('artikel/', 'sixcms/detail.php?id=')
|
||||
return newurl + '&_druckversion=1'
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Spektrum der Wissenschaft', u'http://www.spektrum.de/artikel/982623'),
|
||||
(u'SpektrumDirekt', u'http://www.spektrumdirekt.de/artikel/996406'),
|
||||
(u'Sterne und Weltraum', u'http://www.astronomie-heute.de/artikel/865248'),
|
||||
(u'Gehirn & Geist', u'http://www.gehirn-und-geist.de/artikel/982626'),
|
||||
(u'epoc', u'http://www.epoc.de/artikel/982625')
|
||||
|
||||
]
|
||||
|
||||
filter_regexps = [r'ads\.doubleclick\.net']
|
24
recipes/technology_review_de.recipe
Normal file
24
recipes/technology_review_de.recipe
Normal file
@ -0,0 +1,24 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
|
||||
title = u'Technology Review'
|
||||
__author__ = 'schuster'
|
||||
remove_tags_before = dict(id='keywords')
|
||||
remove_tags_after = dict(id='kommentar')
|
||||
remove_tags = [dict(attrs={'class':['navi_oben_pvg', 'navi_oben_tarifr', 'navi_oben_itm', 'navi_oben_eve', 'navi_oben_whi', 'navi_oben_abo', 'navi_oben_shop', 'navi_top_logo', 'navi_top_abschnitt', 'first']}),
|
||||
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']),
|
||||
dict(name=['script', 'noscript', 'style'])]
|
||||
oldest_article = 4
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?view=print'
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Technik News', u'http://www.heise.de/tr/news-atom.xml') ]
|
||||
|
@ -14,6 +14,7 @@ class UnitedDaily(BasicNewsRecipe):
|
||||
(u'生活', u'http://udn.com/udnrss/life.xml'),
|
||||
(u'綜合', u'http://udn.com/udnrss/education.xml'),
|
||||
(u'意見評論', u'http://udn.com/udnrss/opinion.xml'),
|
||||
(u'校園博覽會', u'http://mag.udn.com/udnrss/campus_rss.xml'),
|
||||
(u'大台北', u'http://udn.com/udnrss/local_taipei.xml'),
|
||||
(u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'),
|
||||
(u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'),
|
||||
@ -21,15 +22,21 @@ class UnitedDaily(BasicNewsRecipe):
|
||||
(u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'),
|
||||
(u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'),
|
||||
(u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'),
|
||||
(u'台灣人物', u'http://mag.udn.com/udnrss/people_rss.xml'),
|
||||
(u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'),
|
||||
(u'國際焦點', u'http://udn.com/udnrss/international.xml'),
|
||||
(u'台商經貿', u'http://udn.com/udnrss/financechina.xml'),
|
||||
(u'國際財經', u'http://udn.com/udnrss/financeworld.xml'),
|
||||
(u'全球觀察', u'http://mag.udn.com/udnrss/world_rss.xml'),
|
||||
(u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'),
|
||||
(u'股市要聞', u'http://udn.com/udnrss/stock.xml'),
|
||||
(u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'),
|
||||
(u'稅務法務', u'http://udn.com/udnrss/tax.xml'),
|
||||
(u'房市情報', u'http://udn.com/udnrss/houses.xml'),
|
||||
(u'個人理財', u'http://mag.udn.com/udnrss/wealth_rss.xml'),
|
||||
(u'研究報告', u'http://mag.udn.com/udnrss/report_rss.xml'),
|
||||
(u'基金', u'http://mag.udn.com/udnrss/fund_rss.xml'),
|
||||
(u'理財會客室', u'http://mag.udn.com/udnrss/m_forum_rss.xml'),
|
||||
(u'棒球', u'http://udn.com/udnrss/baseball.xml'),
|
||||
(u'籃球', u'http://udn.com/udnrss/basketball.xml'),
|
||||
(u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'),
|
||||
@ -40,19 +47,24 @@ class UnitedDaily(BasicNewsRecipe):
|
||||
(u'電影世界', u'http://udn.com/udnrss/movie.xml'),
|
||||
(u'流行音樂', u'http://udn.com/udnrss/music.xml'),
|
||||
(u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'),
|
||||
(u'消費流行', u'http://mag.udn.com/udnrss/happylife_rss.xml'),
|
||||
(u'食樂指南', u'http://udn.com/udnrss/food.xml'),
|
||||
(u'數位資訊', u'http://mag.udn.com/udnrss/digital_rss.xml'),
|
||||
(u'折扣好康', u'http://udn.com/udnrss/shopping.xml'),
|
||||
(u'發燒車訊', u'http://mag.udn.com/udnrss/car_rss.xml'),
|
||||
(u'醫藥新聞', u'http://udn.com/udnrss/health.xml'),
|
||||
(u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'),
|
||||
(u'談星論命', u'http://udn.com/udnrss/astrology.xml'),
|
||||
(u'文化副刊', u'http://udn.com/udnrss/reading.xml'),
|
||||
(u'旅遊休閒', u'http://travel.udn.com/udnrss/travel_rss.xml'),
|
||||
(u'健康醫藥', u'http://mag.udn.com/udnrss/life_rss.xml'),
|
||||
]
|
||||
|
||||
extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;}'''
|
||||
extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] td[class='story_title']>div {font-size:200%; font-weight:bold;}'''
|
||||
|
||||
__author__ = 'Eddie Lau'
|
||||
__version__ = '1.0'
|
||||
language = 'zh'
|
||||
__version__ = '1.1'
|
||||
language = 'zh-TW'
|
||||
publisher = 'United Daily News Group'
|
||||
description = 'United Daily (Taiwan)'
|
||||
category = 'News, Chinese, Taiwan'
|
||||
@ -63,5 +75,12 @@ class UnitedDaily(BasicNewsRecipe):
|
||||
conversion_options = {'linearize_tables':True}
|
||||
masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
||||
cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif'
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['story_title','story_author', 'story']})]
|
||||
keep_only_tags = [dict(name='td', attrs={'class':['story_title']}),
|
||||
dict(name='div', attrs={'id':['story_title']}),
|
||||
dict(name='td', attrs={'class':['story_author']}),
|
||||
dict(name='div', attrs={'id':['story_author']}),
|
||||
dict(name='td', attrs={'class':['story']}),
|
||||
dict(name='div', attrs={'id':['story']}),
|
||||
]
|
||||
remove_tags = [dict(name='div', attrs={'id':['mvouter']})]
|
||||
|
||||
|
@ -41,14 +41,19 @@ authors_completer_append_separator = False
|
||||
#: Author sort name algorithm
|
||||
# The algorithm used to copy author to author_sort
|
||||
# Possible values are:
|
||||
# invert: use "fn ln" -> "ln, fn" (the default algorithm)
|
||||
# invert: use "fn ln" -> "ln, fn"
|
||||
# copy : copy author to author_sort without modification
|
||||
# comma : use 'copy' if there is a ',' in the name, otherwise use 'invert'
|
||||
# nocomma : "fn ln" -> "ln fn" (without the comma)
|
||||
# When this tweak is changed, the author_sort values stored with each author
|
||||
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
||||
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
||||
# The author name suffixes are words that are ignored when they occur at the
|
||||
# end of an author name. The case of the suffix is ignored and trailing
|
||||
# periods are automatically handled.
|
||||
author_sort_copy_method = 'comma'
|
||||
author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd',
|
||||
'MD', 'M.D', 'I', 'II', 'III', 'IV')
|
||||
|
||||
#: Use author sort in Tag Browser
|
||||
# Set which author field to display in the tags pane (the list of authors,
|
||||
|
@ -32,7 +32,6 @@ class Win32(VMInstaller):
|
||||
FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice'
|
||||
INSTALLER_EXT = 'msi'
|
||||
SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0']
|
||||
BUILD_BUILD = ['python setup.py kakasi',] + VMInstaller.BUILD_BUILD
|
||||
|
||||
def download_installer(self):
|
||||
installer = self.installer()
|
||||
|
@ -6,10 +6,10 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, cPickle, re, anydbm, shutil, marshal, zipfile, glob
|
||||
import os, cPickle, re, shutil, marshal, zipfile, glob
|
||||
from zlib import compress
|
||||
|
||||
from setup import Command, basenames, __appname__, iswindows
|
||||
from setup import Command, basenames, __appname__
|
||||
|
||||
def get_opts_from_parser(parser):
|
||||
def do_opt(opt):
|
||||
@ -34,12 +34,12 @@ class Kakasi(Command):
|
||||
self.records = {}
|
||||
src = self.j(self.KAKASI_PATH, 'kakasidict.utf8')
|
||||
dest = self.j(self.RESOURCES, 'localization',
|
||||
'pykakasi','kanwadict2.db')
|
||||
'pykakasi','kanwadict2.pickle')
|
||||
base = os.path.dirname(dest)
|
||||
if not os.path.exists(base):
|
||||
os.makedirs(base)
|
||||
|
||||
if self.newer(dest, src) or iswindows:
|
||||
if self.newer(dest, src):
|
||||
self.info('\tGenerating Kanwadict')
|
||||
|
||||
for line in open(src, "r"):
|
||||
@ -50,7 +50,7 @@ class Kakasi(Command):
|
||||
dest = self.j(self.RESOURCES, 'localization',
|
||||
'pykakasi','itaijidict2.pickle')
|
||||
|
||||
if self.newer(dest, src) or iswindows:
|
||||
if self.newer(dest, src):
|
||||
self.info('\tGenerating Itaijidict')
|
||||
self.mkitaiji(src, dest)
|
||||
|
||||
@ -58,7 +58,7 @@ class Kakasi(Command):
|
||||
dest = self.j(self.RESOURCES, 'localization',
|
||||
'pykakasi','kanadict2.pickle')
|
||||
|
||||
if self.newer(dest, src) or iswindows:
|
||||
if self.newer(dest, src):
|
||||
self.info('\tGenerating kanadict')
|
||||
self.mkkanadict(src, dest)
|
||||
|
||||
@ -75,7 +75,7 @@ class Kakasi(Command):
|
||||
continue
|
||||
pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line)
|
||||
dic[pair[0]] = pair[1]
|
||||
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle
|
||||
cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
|
||||
|
||||
def mkkanadict(self, src, dst):
|
||||
dic = {}
|
||||
@ -87,7 +87,7 @@ class Kakasi(Command):
|
||||
continue
|
||||
(alpha, kana) = line.split(' ')
|
||||
dic[kana] = alpha
|
||||
cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle
|
||||
cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle
|
||||
|
||||
def parsekdict(self, line):
|
||||
line = line.decode("utf-8").strip()
|
||||
@ -115,16 +115,11 @@ class Kakasi(Command):
|
||||
self.records[key][kanji]=[(yomi, tail)]
|
||||
|
||||
def kanwaout(self, out):
|
||||
try:
|
||||
# Needed as otherwise anydbm tries to create a gdbm db when the db
|
||||
# created on Unix is found
|
||||
os.remove(out)
|
||||
except:
|
||||
pass
|
||||
dic = anydbm.open(out, 'n')
|
||||
for (k, v) in self.records.iteritems():
|
||||
with open(out, 'wb') as f:
|
||||
dic = {}
|
||||
for k, v in self.records.iteritems():
|
||||
dic[k] = compress(marshal.dumps(v))
|
||||
dic.close()
|
||||
cPickle.dump(dic, f, -1)
|
||||
|
||||
def clean(self):
|
||||
kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi')
|
||||
|
@ -630,6 +630,24 @@ def human_readable(size):
|
||||
size = size[:-2]
|
||||
return size + " " + suffix
|
||||
|
||||
def remove_bracketed_text(src,
|
||||
brackets={u'(':u')', u'[':u']', u'{':u'}'}):
|
||||
from collections import Counter
|
||||
counts = Counter()
|
||||
buf = []
|
||||
src = force_unicode(src)
|
||||
rmap = dict([(v, k) for k, v in brackets.iteritems()])
|
||||
for char in src:
|
||||
if char in brackets:
|
||||
counts[char] += 1
|
||||
elif char in rmap:
|
||||
idx = rmap[char]
|
||||
if counts[idx] > 0:
|
||||
counts[idx] -= 1
|
||||
elif sum(counts.itervalues()) < 1:
|
||||
buf.append(char)
|
||||
return u''.join(buf)
|
||||
|
||||
if isosx:
|
||||
import glob, shutil
|
||||
fdir = os.path.expanduser('~/.fonts')
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os.path
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
@ -1094,19 +1095,25 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
|
||||
# Store plugins {{{
|
||||
class StoreAmazonKindleStore(StoreBase):
|
||||
name = 'Amazon Kindle'
|
||||
description = _('Kindle books from Amazon')
|
||||
description = _('Kindle books from Amazon.')
|
||||
actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore'
|
||||
|
||||
class StoreAmazonDEKindleStore(StoreBase):
|
||||
name = 'Amazon DE Kindle'
|
||||
description = _('Kindle eBooks')
|
||||
description = _('Kindle books from Amazon.de.')
|
||||
actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore'
|
||||
|
||||
class StoreAmazonUKKindleStore(StoreBase):
|
||||
name = 'Amazon UK Kindle'
|
||||
description = _('Kindle books from Amazon.uk')
|
||||
description = _('Kindle books from Amazon.uk.')
|
||||
actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore'
|
||||
|
||||
class StoreArchiveOrgStore(StoreBase):
|
||||
name = 'Archive.org'
|
||||
description = _('Free Books : Download & Streaming : Ebook and Texts Archive : Internet Archive.')
|
||||
actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore'
|
||||
|
||||
|
||||
class StoreBaenWebScriptionStore(StoreBase):
|
||||
name = 'Baen WebScription'
|
||||
description = _('Ebooks for readers.')
|
||||
@ -1119,7 +1126,7 @@ class StoreBNStore(StoreBase):
|
||||
|
||||
class StoreBeamEBooksDEStore(StoreBase):
|
||||
name = 'Beam EBooks DE'
|
||||
description = _('der eBook Shop')
|
||||
description = _('Der eBook Shop.')
|
||||
actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore'
|
||||
|
||||
class StoreBeWriteStore(StoreBase):
|
||||
@ -1139,12 +1146,12 @@ class StoreEbookscomStore(StoreBase):
|
||||
|
||||
class StoreEPubBuyDEStore(StoreBase):
|
||||
name = 'EPUBBuy DE'
|
||||
description = _('EPUBReaders eBook Shop')
|
||||
description = _('EPUBReaders eBook Shop.')
|
||||
actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore'
|
||||
|
||||
class StoreEHarlequinStore(StoreBase):
|
||||
name = 'eHarlequin'
|
||||
description = _('entertain, enrich, inspire.')
|
||||
description = _('Entertain, enrich, inspire.')
|
||||
actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore'
|
||||
|
||||
class StoreFeedbooksStore(StoreBase):
|
||||
@ -1154,9 +1161,14 @@ class StoreFeedbooksStore(StoreBase):
|
||||
|
||||
class StoreFoylesUKStore(StoreBase):
|
||||
name = 'Foyles UK'
|
||||
description = _('Foyles of London, online')
|
||||
description = _('Foyles of London, online.')
|
||||
actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore'
|
||||
|
||||
class StoreGoogleBooksStore(StoreBase):
|
||||
name = 'Google Books'
|
||||
description = _('Google Books')
|
||||
actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore'
|
||||
|
||||
class StoreGutenbergStore(StoreBase):
|
||||
name = 'Project Gutenberg'
|
||||
description = _('The first producer of free ebooks.')
|
||||
@ -1174,9 +1186,14 @@ class StoreManyBooksStore(StoreBase):
|
||||
|
||||
class StoreMobileReadStore(StoreBase):
|
||||
name = 'MobileRead'
|
||||
description = _('Ebooks handcrafted with the utmost care')
|
||||
description = _('Ebooks handcrafted with the utmost care.')
|
||||
actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore'
|
||||
|
||||
class StoreNextoStore(StoreBase):
|
||||
name = 'Nexto'
|
||||
description = _('Audiobooki mp3, ebooki, prasa - księgarnia internetowa.')
|
||||
actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore'
|
||||
|
||||
class StoreOpenLibraryStore(StoreBase):
|
||||
name = 'Open Library'
|
||||
description = _('One web page for every book.')
|
||||
@ -1189,26 +1206,27 @@ class StoreSmashwordsStore(StoreBase):
|
||||
|
||||
class StoreWaterstonesUKStore(StoreBase):
|
||||
name = 'Waterstones UK'
|
||||
description = _('Feel every word')
|
||||
description = _('Feel every word.')
|
||||
actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore'
|
||||
|
||||
class StoreWeightlessBooksStore(StoreBase):
|
||||
name = 'Weightless Books'
|
||||
description = '(e)Books That Don\'t Weigh You Down'
|
||||
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'
|
||||
description = 'Wizard\'s Tower Press.'
|
||||
actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore'
|
||||
|
||||
plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore,
|
||||
StoreBaenWebScriptionStore, StoreBNStore,
|
||||
plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleStore,
|
||||
StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore,
|
||||
StoreBeamEBooksDEStore, StoreBeWriteStore,
|
||||
StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore,
|
||||
StoreEHarlequinStore, StoreFeedbooksStore,
|
||||
StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore,
|
||||
StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore,
|
||||
StoreFoylesUKStore, StoreGoogleBooksStore, StoreGutenbergStore,
|
||||
StoreKoboStore, StoreManyBooksStore,
|
||||
StoreMobileReadStore, StoreNextoStore, StoreOpenLibraryStore, StoreSmashwordsStore,
|
||||
StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore]
|
||||
|
||||
# }}}
|
||||
|
@ -10,7 +10,7 @@ import os, sys, re
|
||||
from urllib import unquote, quote
|
||||
from urlparse import urlparse
|
||||
|
||||
from calibre import relpath, guess_type
|
||||
from calibre import relpath, guess_type, remove_bracketed_text
|
||||
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
@ -27,20 +27,37 @@ def authors_to_string(authors):
|
||||
else:
|
||||
return ''
|
||||
|
||||
_bracket_pat = re.compile(r'[\[({].*?[})\]]')
|
||||
def author_to_author_sort(author):
|
||||
def author_to_author_sort(author, method=None):
|
||||
if not author:
|
||||
return ''
|
||||
method = tweaks['author_sort_copy_method']
|
||||
if method == 'copy' or (method == 'comma' and ',' in author):
|
||||
return u''
|
||||
sauthor = remove_bracketed_text(author).strip()
|
||||
tokens = sauthor.split()
|
||||
if len(tokens) < 2:
|
||||
return author
|
||||
author = _bracket_pat.sub('', author).strip()
|
||||
tokens = author.split()
|
||||
if tokens and tokens[-1] not in ('Inc.', 'Inc'):
|
||||
tokens = tokens[-1:] + tokens[:-1]
|
||||
if len(tokens) > 1 and method != 'nocomma':
|
||||
tokens[0] += ','
|
||||
return ' '.join(tokens)
|
||||
if method is None:
|
||||
method = tweaks['author_sort_copy_method']
|
||||
if method == u'copy':
|
||||
return author
|
||||
suffixes = set([x.lower() for x in tweaks['author_name_suffixes']])
|
||||
suffixes |= set([x+u'.' for x in suffixes])
|
||||
|
||||
last = tokens[-1].lower()
|
||||
suffix = None
|
||||
if last in suffixes:
|
||||
suffix = tokens[-1]
|
||||
tokens = tokens[:-1]
|
||||
|
||||
if method == u'comma' and u',' in u''.join(tokens):
|
||||
return author
|
||||
|
||||
atokens = tokens[-1:] + tokens[:-1]
|
||||
if suffix:
|
||||
atokens.append(suffix)
|
||||
|
||||
if method != u'nocomma' and len(atokens) > 1:
|
||||
atokens[0] += u','
|
||||
|
||||
return u' '.join(atokens)
|
||||
|
||||
def authors_to_sort_string(authors):
|
||||
return ' & '.join(map(author_to_author_sort, authors))
|
||||
|
@ -16,7 +16,7 @@ from lxml.html import soupparser, tostring
|
||||
|
||||
from calibre import as_unicode
|
||||
from calibre.ebooks.metadata import check_isbn
|
||||
from calibre.ebooks.metadata.sources.base import Source
|
||||
from calibre.ebooks.metadata.sources.base import Source, Option
|
||||
from calibre.utils.cleantext import clean_ascii_chars
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
@ -37,6 +37,92 @@ class Worker(Thread): # Get details {{{
|
||||
self.relevance, self.plugin = relevance, plugin
|
||||
self.browser = browser.clone_browser()
|
||||
self.cover_url = self.amazon_id = self.isbn = None
|
||||
self.domain = self.plugin.domain
|
||||
|
||||
months = {
|
||||
'de': {
|
||||
1 : ['jän'],
|
||||
3 : ['märz'],
|
||||
5 : ['mai'],
|
||||
6 : ['juni'],
|
||||
7 : ['juli'],
|
||||
10: ['okt'],
|
||||
12: ['dez']
|
||||
},
|
||||
'it': {
|
||||
1: ['enn'],
|
||||
2: ['febbr'],
|
||||
5: ['magg'],
|
||||
6: ['giugno'],
|
||||
7: ['luglio'],
|
||||
8: ['ag'],
|
||||
9: ['sett'],
|
||||
10: ['ott'],
|
||||
12: ['dic'],
|
||||
},
|
||||
'fr': {
|
||||
1: ['janv'],
|
||||
2: ['févr'],
|
||||
3: ['mars'],
|
||||
4: ['avril'],
|
||||
5: ['mai'],
|
||||
6: ['juin'],
|
||||
7: ['juil'],
|
||||
8: ['août'],
|
||||
9: ['sept'],
|
||||
12: ['déc'],
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
self.english_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
self.months = months.get(self.domain, {})
|
||||
|
||||
self.pd_xpath = '''
|
||||
//h2[text()="Product Details" or \
|
||||
text()="Produktinformation" or \
|
||||
text()="Dettagli prodotto" or \
|
||||
text()="Product details" or \
|
||||
text()="Détails sur le produit"]/../div[@class="content"]
|
||||
'''
|
||||
self.publisher_xpath = '''
|
||||
descendant::*[starts-with(text(), "Publisher:") or \
|
||||
starts-with(text(), "Verlag:") or \
|
||||
starts-with(text(), "Editore:") or \
|
||||
starts-with(text(), "Editeur")]
|
||||
'''
|
||||
self.language_xpath = '''
|
||||
descendant::*[
|
||||
starts-with(text(), "Language:") \
|
||||
or text() = "Language" \
|
||||
or text() = "Sprache:" \
|
||||
or text() = "Lingua:" \
|
||||
or starts-with(text(), "Langue") \
|
||||
]
|
||||
'''
|
||||
self.ratings_pat = re.compile(
|
||||
r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}')
|
||||
|
||||
lm = {
|
||||
'en': ('English', 'Englisch'),
|
||||
'fr': ('French', 'Français'),
|
||||
'it': ('Italian', 'Italiano'),
|
||||
'de': ('German', 'Deutsch'),
|
||||
}
|
||||
self.lang_map = {}
|
||||
for code, names in lm.iteritems():
|
||||
for name in names:
|
||||
self.lang_map[name] = code
|
||||
|
||||
def delocalize_datestr(self, raw):
|
||||
if not self.months:
|
||||
return raw
|
||||
ans = raw.lower()
|
||||
for i, vals in self.months.iteritems():
|
||||
for x in vals:
|
||||
ans = ans.replace(x, self.english_months[i])
|
||||
return ans
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
@ -132,7 +218,7 @@ class Worker(Thread): # Get details {{{
|
||||
self.log.exception('Error parsing cover for url: %r'%self.url)
|
||||
mi.has_cover = bool(self.cover_url)
|
||||
|
||||
pd = root.xpath('//h2[text()="Product Details"]/../div[@class="content"]')
|
||||
pd = root.xpath(self.pd_xpath)
|
||||
if pd:
|
||||
pd = pd[0]
|
||||
|
||||
@ -194,23 +280,29 @@ class Worker(Thread): # Get details {{{
|
||||
def parse_authors(self, root):
|
||||
x = '//h1[@class="parseasinTitle"]/following-sibling::span/*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]'
|
||||
aname = root.xpath(x)
|
||||
if not aname:
|
||||
aname = root.xpath('''
|
||||
//h1[@class="parseasinTitle"]/following-sibling::*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]
|
||||
''')
|
||||
for x in aname:
|
||||
x.tail = ''
|
||||
authors = [tostring(x, encoding=unicode, method='text').strip() for x
|
||||
in aname]
|
||||
authors = [a for a in authors if a]
|
||||
return authors
|
||||
|
||||
def parse_rating(self, root):
|
||||
ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]')
|
||||
if not ratings:
|
||||
ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]')
|
||||
pat = re.compile(r'([0-9.]+) out of (\d+) stars')
|
||||
if not ratings:
|
||||
ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]')
|
||||
if ratings:
|
||||
for elem in ratings[0].xpath('descendant::*[@title]'):
|
||||
t = elem.get('title').strip()
|
||||
m = pat.match(t)
|
||||
m = self.ratings_pat.match(t)
|
||||
if m is not None:
|
||||
return float(m.group(1))/float(m.group(2)) * 5
|
||||
return float(m.group(1))/float(m.group(3)) * 5
|
||||
|
||||
def parse_comments(self, root):
|
||||
desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]')
|
||||
@ -264,27 +356,26 @@ class Worker(Thread): # Get details {{{
|
||||
return ans
|
||||
|
||||
def parse_publisher(self, pd):
|
||||
for x in reversed(pd.xpath(
|
||||
'descendant::*[starts-with(text(), "Publisher:")]')):
|
||||
for x in reversed(pd.xpath(self.publisher_xpath)):
|
||||
if x.tail:
|
||||
ans = x.tail.partition(';')[0]
|
||||
return ans.partition('(')[0].strip()
|
||||
|
||||
def parse_pubdate(self, pd):
|
||||
for x in reversed(pd.xpath(
|
||||
'descendant::*[starts-with(text(), "Publisher:")]')):
|
||||
for x in reversed(pd.xpath(self.publisher_xpath)):
|
||||
if x.tail:
|
||||
ans = x.tail
|
||||
date = ans.partition('(')[-1].replace(')', '').strip()
|
||||
date = self.delocalize_datestr(date)
|
||||
return parse_date(date, assume_utc=True)
|
||||
|
||||
def parse_language(self, pd):
|
||||
for x in reversed(pd.xpath(
|
||||
'descendant::*[starts-with(text(), "Language:")]')):
|
||||
for x in reversed(pd.xpath(self.language_xpath)):
|
||||
if x.tail:
|
||||
ans = x.tail.strip()
|
||||
if ans == 'English':
|
||||
return 'en'
|
||||
ans = self.lang_map.get(ans, None)
|
||||
if ans:
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
class Amazon(Source):
|
||||
@ -304,8 +395,15 @@ class Amazon(Source):
|
||||
'fr' : _('France'),
|
||||
'de' : _('Germany'),
|
||||
'uk' : _('UK'),
|
||||
'it' : _('Italy'),
|
||||
}
|
||||
|
||||
options = (
|
||||
Option('domain', 'choices', 'com', _('Amazon website to use:'),
|
||||
_('Metadata from Amazon will be fetched using this '
|
||||
'country\'s Amazon website.'), choices=AMAZON_DOMAINS),
|
||||
)
|
||||
|
||||
def get_book_url(self, identifiers): # {{{
|
||||
asin = identifiers.get('amazon', None)
|
||||
if asin is None:
|
||||
@ -314,8 +412,16 @@ class Amazon(Source):
|
||||
return ('amazon', asin, 'http://amzn.com/%s'%asin)
|
||||
# }}}
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
domain = self.prefs['domain']
|
||||
if domain not in self.AMAZON_DOMAINS:
|
||||
domain = 'com'
|
||||
|
||||
return domain
|
||||
|
||||
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
||||
domain = self.prefs.get('domain', 'com')
|
||||
domain = self.domain
|
||||
|
||||
# See the amazon detailed search page to get all options
|
||||
q = { 'search-alias' : 'aps',
|
||||
@ -355,6 +461,8 @@ class Amazon(Source):
|
||||
latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1',
|
||||
'ignore')) for x, y in
|
||||
q.iteritems()])
|
||||
if domain == 'uk':
|
||||
domain = 'co.uk'
|
||||
url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q)
|
||||
return url
|
||||
|
||||
@ -526,8 +634,7 @@ if __name__ == '__main__': # tests {{{
|
||||
# src/calibre/ebooks/metadata/sources/amazon.py
|
||||
from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
|
||||
title_test, authors_test)
|
||||
test_identify_plugin(Amazon.name,
|
||||
[
|
||||
com_tests = [ # {{{
|
||||
|
||||
( # Description has links
|
||||
{'identifiers':{'isbn': '9780671578275'}},
|
||||
@ -575,6 +682,38 @@ if __name__ == '__main__': # tests {{{
|
||||
|
||||
),
|
||||
|
||||
])
|
||||
] # }}}
|
||||
|
||||
de_tests = [ # {{{
|
||||
(
|
||||
{'identifiers':{'isbn': '3548283519'}},
|
||||
[title_test('Wer Wind sät',
|
||||
exact=True), authors_test(['Nele Neuhaus'])
|
||||
]
|
||||
|
||||
),
|
||||
] # }}}
|
||||
|
||||
it_tests = [ # {{{
|
||||
(
|
||||
{'identifiers':{'isbn': '8838922195'}},
|
||||
[title_test('La briscola in cinque',
|
||||
exact=True), authors_test(['Marco Malvaldi'])
|
||||
]
|
||||
|
||||
),
|
||||
] # }}}
|
||||
|
||||
fr_tests = [ # {{{
|
||||
(
|
||||
{'identifiers':{'isbn': '2221116798'}},
|
||||
[title_test('L\'étrange voyage de Monsieur Daldry',
|
||||
exact=True), authors_test(['Marc Levy'])
|
||||
]
|
||||
|
||||
),
|
||||
] # }}}
|
||||
|
||||
test_identify_plugin(Amazon.name, com_tests)
|
||||
# }}}
|
||||
|
||||
|
@ -145,10 +145,13 @@ class Option(object):
|
||||
:param default: The default value for this option
|
||||
:param label: A short (few words) description of this option
|
||||
:param desc: A longer description of this option
|
||||
:param choices: A list of possible values, used only if type='choices'
|
||||
:param choices: A dict of possible values, used only if type='choices'.
|
||||
dict is of the form {key:human readable label, ...}
|
||||
'''
|
||||
self.name, self.type, self.default, self.label, self.desc = (name,
|
||||
type_, default, label, desc)
|
||||
if choices and not isinstance(choices, dict):
|
||||
choices = dict([(x, x) for x in choices])
|
||||
self.choices = choices
|
||||
|
||||
class Source(Plugin):
|
||||
|
@ -690,6 +690,14 @@ class MobiReader(object):
|
||||
lm = unit_convert('2em', 12, 500, 166)
|
||||
lm = self.left_margins.get(tag, lm)
|
||||
ti = self.text_indents.get(tag, ti)
|
||||
try:
|
||||
lm = float(lm)
|
||||
except:
|
||||
lm = 0.0
|
||||
try:
|
||||
ti = float(ti)
|
||||
except:
|
||||
ti = 0.0
|
||||
return lm + ti
|
||||
|
||||
parent = tag
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <wand/MagickWand.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "images.h"
|
||||
#include "utils.h"
|
||||
|
@ -2,12 +2,8 @@
|
||||
# jisyo.py
|
||||
#
|
||||
# Copyright 2011 Hiroshi Miura <miurahr@linux.com>
|
||||
from cPickle import load
|
||||
import anydbm,marshal
|
||||
import cPickle, marshal
|
||||
from zlib import decompress
|
||||
import os
|
||||
|
||||
import calibre.utils.resources as resources
|
||||
|
||||
class jisyo (object):
|
||||
kanwadict = None
|
||||
@ -25,16 +21,14 @@ class jisyo (object):
|
||||
|
||||
def __init__(self):
|
||||
if self.kanwadict is None:
|
||||
dictpath = resources.get_path(os.path.join('localization','pykakasi','kanwadict2.db'))
|
||||
self.kanwadict = anydbm.open(dictpath,'r')
|
||||
self.kanwadict = cPickle.loads(
|
||||
P('localization/pykakasi/kanwadict2.pickle', data=True))
|
||||
if self.itaijidict is None:
|
||||
itaijipath = resources.get_path(os.path.join('localization','pykakasi','itaijidict2.pickle'))
|
||||
itaiji_pkl = open(itaijipath, 'rb')
|
||||
self.itaijidict = load(itaiji_pkl)
|
||||
self.itaijidict = cPickle.loads(
|
||||
P('localization/pykakasi/itaijidict2.pickle', data=True))
|
||||
if self.kanadict is None:
|
||||
kanadictpath = resources.get_path(os.path.join('localization','pykakasi','kanadict2.pickle'))
|
||||
kanadict_pkl = open(kanadictpath, 'rb')
|
||||
self.kanadict = load(kanadict_pkl)
|
||||
self.kanadict = cPickle.loads(
|
||||
P('localization/pykakasi/kanadict2.pickle', data=True))
|
||||
|
||||
def load_jisyo(self, char):
|
||||
try:#python2
|
||||
|
@ -625,6 +625,17 @@ class Application(QApplication):
|
||||
if s is not None:
|
||||
font.setStretch(s)
|
||||
QApplication.setFont(font)
|
||||
st = self.style()
|
||||
if st is not None:
|
||||
st = unicode(st.objectName()).lower()
|
||||
if (islinux or isfreebsd) and st in ('windows', 'motif', 'cde'):
|
||||
from PyQt4.Qt import QStyleFactory
|
||||
styles = set(map(unicode, QStyleFactory.keys()))
|
||||
if 'Plastique' in styles and os.environ.get('KDE_FULL_SESSION',
|
||||
False):
|
||||
self.setStyle('Plastique')
|
||||
elif 'Cleanlooks' in styles:
|
||||
self.setStyle('Cleanlooks')
|
||||
|
||||
def _send_file_open_events(self):
|
||||
with self._file_open_lock:
|
||||
|
@ -19,8 +19,9 @@ class PreferencesAction(InterfaceAction):
|
||||
|
||||
def genesis(self):
|
||||
pm = QMenu()
|
||||
acname = _('Change calibre behavior') if isosx else _('Preferences')
|
||||
pm.addAction(QIcon(I('config.png')), acname, self.do_config)
|
||||
pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config)
|
||||
if isosx:
|
||||
pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config)
|
||||
pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'),
|
||||
self.gui.run_wizard)
|
||||
if not DEBUG:
|
||||
|
@ -52,16 +52,23 @@ class StoreAction(InterfaceAction):
|
||||
return rows[0].row()
|
||||
|
||||
def _get_author(self, row):
|
||||
author = ''
|
||||
authors = []
|
||||
|
||||
if self.gui.current_view() is self.gui.library_view:
|
||||
author = self.gui.library_view.model().authors(row)
|
||||
if author:
|
||||
author = author.replace('|', ' ')
|
||||
a = self.gui.library_view.model().authors(row)
|
||||
authors = a.split(',')
|
||||
else:
|
||||
mi = self.gui.current_view().model().get_book_display_info(row)
|
||||
author = ' & '.join(mi.authors)
|
||||
authors = mi.authors
|
||||
|
||||
return author
|
||||
corrected_authors = []
|
||||
for x in authors:
|
||||
a = x.split('|')
|
||||
a.reverse()
|
||||
a = ' '.join(a)
|
||||
corrected_authors.append(a)
|
||||
|
||||
return ' & '.join(corrected_authors).strip()
|
||||
|
||||
def search_author(self):
|
||||
row = self._get_selected_row()
|
||||
@ -80,7 +87,7 @@ class StoreAction(InterfaceAction):
|
||||
mi = self.gui.current_view().model().get_book_display_info(row)
|
||||
title = mi.title
|
||||
|
||||
return title
|
||||
return title.strip()
|
||||
|
||||
def search_title(self):
|
||||
row = self._get_selected_row()
|
||||
|
316
src/calibre/gui2/bars.py
Normal file
316
src/calibre/gui2/bars.py
Normal file
@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout,
|
||||
QLabel, QWidget, QAction, QMenuBar, QMenu)
|
||||
|
||||
from calibre.constants import isosx
|
||||
from calibre.gui2 import gprefs
|
||||
|
||||
class ToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, donate, location_manager, parent):
|
||||
QToolBar.__init__(self, parent)
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
self.setOrientation(Qt.Horizontal)
|
||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||
self.preferred_width = self.sizeHint().width()
|
||||
self.gui = parent
|
||||
self.donate_button = donate
|
||||
self.added_actions = []
|
||||
|
||||
self.location_manager = location_manager
|
||||
donate.setAutoRaise(True)
|
||||
donate.setCursor(Qt.PointingHandCursor)
|
||||
self.setAcceptDrops(True)
|
||||
self.showing_donate = False
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
QToolBar.resizeEvent(self, ev)
|
||||
style = self.get_text_style()
|
||||
self.setToolButtonStyle(style)
|
||||
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
|
||||
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
|
||||
|
||||
def get_text_style(self):
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
s = gprefs['toolbar_icon_size']
|
||||
if s != 'off':
|
||||
p = gprefs['toolbar_text']
|
||||
if p == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
elif p == 'auto' and self.preferred_width > self.width()+35:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
return style
|
||||
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
def update_lm_actions(self):
|
||||
for ac in self.added_actions:
|
||||
if ac in self.location_manager.all_actions:
|
||||
ac.setVisible(ac in self.location_manager.available_actions)
|
||||
|
||||
def init_bar(self, actions):
|
||||
self.showing_donate = False
|
||||
for ac in self.added_actions:
|
||||
m = ac.menu()
|
||||
if m is not None:
|
||||
m.setVisible(False)
|
||||
|
||||
self.clear()
|
||||
self.added_actions = []
|
||||
|
||||
bar = self
|
||||
|
||||
for what in actions:
|
||||
if what is None:
|
||||
bar.addSeparator()
|
||||
elif what == 'Location Manager':
|
||||
for ac in self.location_manager.all_actions:
|
||||
bar.addAction(ac)
|
||||
bar.added_actions.append(ac)
|
||||
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
|
||||
ac.setVisible(False)
|
||||
elif what == 'Donate':
|
||||
self.d_widget = QWidget()
|
||||
self.d_widget.setLayout(QVBoxLayout())
|
||||
self.d_widget.layout().addWidget(self.donate_button)
|
||||
if isosx:
|
||||
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
|
||||
self.d_widget.layout().setContentsMargins(0,0,0,0)
|
||||
self.d_widget.setContentsMargins(0,0,0,0)
|
||||
self.d_widget.filler = QLabel(u'\u00a0')
|
||||
self.d_widget.layout().addWidget(self.d_widget.filler)
|
||||
bar.addWidget(self.d_widget)
|
||||
self.showing_donate = True
|
||||
elif what in self.gui.iactions:
|
||||
action = self.gui.iactions[what]
|
||||
bar.addAction(action.qaction)
|
||||
self.added_actions.append(action.qaction)
|
||||
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
||||
self.preferred_width = self.sizeHint().width()
|
||||
|
||||
def setup_tool_button(self, bar, ac, menu_mode=None):
|
||||
ch = bar.widgetForAction(ac)
|
||||
if ch is None:
|
||||
ch = self.child_bar.widgetForAction(ac)
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
if ac.menu() is not None and menu_mode is not None:
|
||||
ch.setPopupMode(menu_mode)
|
||||
return ch
|
||||
|
||||
#support drag&drop from/to library from/to reader/card
|
||||
def dragEnterEvent(self, event):
|
||||
md = event.mimeData()
|
||||
if md.hasFormat("application/calibre+from_library") or \
|
||||
md.hasFormat("application/calibre+from_device"):
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
allowed = False
|
||||
md = event.mimeData()
|
||||
#Drop is only allowed in the location manager widget's different from the selected one
|
||||
for ac in self.location_manager.available_actions:
|
||||
w = self.widgetForAction(ac)
|
||||
if w is not None:
|
||||
if ( md.hasFormat("application/calibre+from_library") or \
|
||||
md.hasFormat("application/calibre+from_device") ) and \
|
||||
w.geometry().contains(event.pos()) and \
|
||||
isinstance(w, QToolButton) and not w.isChecked():
|
||||
allowed = True
|
||||
break
|
||||
if allowed:
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dropEvent(self, event):
|
||||
data = event.mimeData()
|
||||
|
||||
mime = 'application/calibre+from_library'
|
||||
if data.hasFormat(mime):
|
||||
ids = list(map(int, str(data.data(mime)).split()))
|
||||
tgt = None
|
||||
for ac in self.location_manager.available_actions:
|
||||
w = self.widgetForAction(ac)
|
||||
if w is not None and w.geometry().contains(event.pos()):
|
||||
tgt = ac.calibre_name
|
||||
if tgt is not None:
|
||||
if tgt == 'main':
|
||||
tgt = None
|
||||
self.gui.sync_to_device(tgt, False, send_ids=ids)
|
||||
event.accept()
|
||||
|
||||
mime = 'application/calibre+from_device'
|
||||
if data.hasFormat(mime):
|
||||
paths = [unicode(u.toLocalFile()) for u in data.urls()]
|
||||
if paths:
|
||||
self.gui.iactions['Add Books'].add_books_from_device(
|
||||
self.gui.current_view(), paths=paths)
|
||||
event.accept()
|
||||
|
||||
# }}}
|
||||
|
||||
class MenuAction(QAction): # {{{
|
||||
|
||||
def __init__(self, clone, parent):
|
||||
QAction.__init__(self, clone.text(), parent)
|
||||
self.clone = clone
|
||||
clone.changed.connect(self.clone_changed)
|
||||
|
||||
def clone_changed(self):
|
||||
self.setText(self.clone.text())
|
||||
# }}}
|
||||
|
||||
class MenuBar(QMenuBar): # {{{
|
||||
|
||||
def __init__(self, location_manager, parent):
|
||||
QMenuBar.__init__(self, parent)
|
||||
self.gui = parent
|
||||
self.setNativeMenuBar(True)
|
||||
|
||||
self.location_manager = location_manager
|
||||
self.added_actions = []
|
||||
|
||||
self.donate_action = QAction(_('Donate'), self)
|
||||
self.donate_menu = QMenu()
|
||||
self.donate_menu.addAction(self.gui.donate_action)
|
||||
self.donate_action.setMenu(self.donate_menu)
|
||||
|
||||
def update_lm_actions(self):
|
||||
for ac in self.added_actions:
|
||||
if ac in self.location_manager.all_actions:
|
||||
ac.setVisible(ac in self.location_manager.available_actions)
|
||||
|
||||
def init_bar(self, actions):
|
||||
for ac in self.added_actions:
|
||||
m = ac.menu()
|
||||
if m is not None:
|
||||
m.setVisible(False)
|
||||
|
||||
self.clear()
|
||||
self.added_actions = []
|
||||
|
||||
for what in actions:
|
||||
if what is None:
|
||||
continue
|
||||
elif what == 'Location Manager':
|
||||
for ac in self.location_manager.all_actions:
|
||||
ac = self.build_menu(ac)
|
||||
self.addAction(ac)
|
||||
self.added_actions.append(ac)
|
||||
ac.setVisible(False)
|
||||
elif what == 'Donate':
|
||||
self.addAction(self.donate_action)
|
||||
elif what in self.gui.iactions:
|
||||
action = self.gui.iactions[what]
|
||||
ac = self.build_menu(action.qaction)
|
||||
self.addAction(ac)
|
||||
self.added_actions.append(ac)
|
||||
|
||||
def build_menu(self, action):
|
||||
m = action.menu()
|
||||
ac = MenuAction(action, self)
|
||||
if m is None:
|
||||
m = QMenu()
|
||||
m.addAction(action)
|
||||
ac.setMenu(m)
|
||||
return ac
|
||||
|
||||
# }}}
|
||||
|
||||
class BarsManager(QObject):
|
||||
|
||||
def __init__(self, donate_button, location_manager, parent):
|
||||
QObject.__init__(self, parent)
|
||||
self.donate_button, self.location_manager = (donate_button,
|
||||
location_manager)
|
||||
|
||||
bars = [ToolBar(donate_button, location_manager, parent) for i in
|
||||
range(3)]
|
||||
self.main_bars = tuple(bars[:2])
|
||||
self.child_bars = tuple(bars[2:])
|
||||
|
||||
|
||||
self.apply_settings()
|
||||
self.init_bars()
|
||||
|
||||
def database_changed(self, db):
|
||||
pass
|
||||
|
||||
@property
|
||||
def bars(self):
|
||||
for x in self.main_bars + self.child_bars:
|
||||
yield x
|
||||
|
||||
@property
|
||||
def showing_donate(self):
|
||||
for b in self.bars:
|
||||
if b.isVisible() and b.showing_donate:
|
||||
return True
|
||||
return False
|
||||
|
||||
def init_bars(self):
|
||||
self.bar_actions = tuple(
|
||||
[gprefs['action-layout-toolbar'+x] for x in ('', '-device')] +
|
||||
[gprefs['action-layout-toolbar-child']] +
|
||||
[gprefs['action-layout-menubar']] +
|
||||
[gprefs['action-layout-menubar-device']]
|
||||
)
|
||||
|
||||
for bar, actions in zip(self.bars, self.bar_actions[:3]):
|
||||
bar.init_bar(actions)
|
||||
|
||||
def update_bars(self):
|
||||
'''
|
||||
This shows the correct main toolbar and rebuilds the menubar based on
|
||||
whether a device is connected or not. Note that the toolbars are
|
||||
explicitly not rebuilt, this is to workaround a Qt limitation iwth
|
||||
QToolButton's popup menus and modal dialogs. If you want the toolbars
|
||||
rebuilt, call init_bars().
|
||||
'''
|
||||
showing_device = self.location_manager.has_device
|
||||
main_bar = self.main_bars[1 if showing_device else 0]
|
||||
child_bar = self.child_bars[0]
|
||||
for bar in self.bars:
|
||||
bar.setVisible(False)
|
||||
bar.update_lm_actions()
|
||||
if main_bar.added_actions:
|
||||
main_bar.setVisible(True)
|
||||
if child_bar.added_actions:
|
||||
child_bar.setVisible(True)
|
||||
|
||||
self.menu_bar = MenuBar(self.location_manager, self.parent())
|
||||
self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3])
|
||||
self.menu_bar.update_lm_actions()
|
||||
self.menu_bar.setVisible(bool(self.menu_bar.added_actions))
|
||||
self.parent().setMenuBar(self.menu_bar)
|
||||
|
||||
def apply_settings(self):
|
||||
sz = gprefs['toolbar_icon_size']
|
||||
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
if sz > 0 and gprefs['toolbar_text'] == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
|
||||
for bar in self.bars:
|
||||
bar.setIconSize(QSize(sz, sz))
|
||||
bar.setToolButtonStyle(style)
|
||||
self.donate_button.set_normal_icon_size(sz, sz)
|
||||
|
||||
|
@ -751,6 +751,7 @@ class DeviceMixin(object): # {{{
|
||||
if self.current_view() != self.library_view:
|
||||
self.book_details.reset_info()
|
||||
self.location_manager.update_devices()
|
||||
self.bars_manager.update_bars()
|
||||
self.library_view.set_device_connected(self.device_connected)
|
||||
self.refresh_ondevice()
|
||||
device_signals.device_connection_changed.emit(connected)
|
||||
@ -764,6 +765,7 @@ class DeviceMixin(object): # {{{
|
||||
info, cp, fs = job.result
|
||||
self.location_manager.update_devices(cp, fs,
|
||||
self.device_manager.device.icon)
|
||||
self.bars_manager.update_bars()
|
||||
self.status_bar.device_connected(info[0])
|
||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||
|
||||
|
@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize,
|
||||
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction,
|
||||
from PyQt4.Qt import (QIcon, Qt, QWidget, QSize,
|
||||
pyqtSignal, QToolButton, QMenu,
|
||||
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
|
||||
|
||||
|
||||
from calibre.constants import __appname__, isosx
|
||||
from calibre.constants import __appname__
|
||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||
from calibre.gui2.throbber import ThrobbingButton
|
||||
from calibre.gui2 import gprefs
|
||||
from calibre.gui2.bars import BarsManager
|
||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||
from calibre import human_readable
|
||||
|
||||
@ -35,6 +35,8 @@ class LocationManager(QObject): # {{{
|
||||
self._mem = []
|
||||
self.tooltips = {}
|
||||
|
||||
self.all_actions = []
|
||||
|
||||
def ac(name, text, icon, tooltip):
|
||||
icon = QIcon(I(icon))
|
||||
ac = self.location_actions.addAction(icon, text)
|
||||
@ -59,6 +61,7 @@ class LocationManager(QObject): # {{{
|
||||
ac.setMenu(m)
|
||||
ac.calibre_name = name
|
||||
|
||||
self.all_actions.append(ac)
|
||||
return ac
|
||||
|
||||
self.library_action = ac('library', _('Library'), 'lt.png',
|
||||
@ -234,259 +237,6 @@ class Spacer(QWidget): # {{{
|
||||
self.l.addStretch(10)
|
||||
# }}}
|
||||
|
||||
class MenuAction(QAction): # {{{
|
||||
|
||||
def __init__(self, clone, parent):
|
||||
QAction.__init__(self, clone.text(), parent)
|
||||
self.clone = clone
|
||||
clone.changed.connect(self.clone_changed)
|
||||
|
||||
def clone_changed(self):
|
||||
self.setText(self.clone.text())
|
||||
# }}}
|
||||
|
||||
class MenuBar(QMenuBar): # {{{
|
||||
|
||||
def __init__(self, location_manager, parent):
|
||||
QMenuBar.__init__(self, parent)
|
||||
self.gui = parent
|
||||
self.setNativeMenuBar(True)
|
||||
|
||||
self.location_manager = location_manager
|
||||
self.location_manager.locations_changed.connect(self.build_bar)
|
||||
self.added_actions = []
|
||||
|
||||
self.donate_action = QAction(_('Donate'), self)
|
||||
self.donate_menu = QMenu()
|
||||
self.donate_menu.addAction(self.gui.donate_action)
|
||||
self.donate_action.setMenu(self.donate_menu)
|
||||
self.build_bar()
|
||||
|
||||
def build_bar(self, changed_action=None):
|
||||
showing_device = self.location_manager.has_device
|
||||
actions = '-device' if showing_device else ''
|
||||
actions = gprefs['action-layout-menubar'+actions]
|
||||
|
||||
show_main = len(actions) > 0
|
||||
self.setVisible(show_main)
|
||||
|
||||
for ac in self.added_actions:
|
||||
m = ac.menu()
|
||||
if m is not None:
|
||||
m.setVisible(False)
|
||||
|
||||
self.clear()
|
||||
self.added_actions = []
|
||||
self.action_map = {}
|
||||
|
||||
for what in actions:
|
||||
if what is None:
|
||||
continue
|
||||
elif what == 'Location Manager':
|
||||
for ac in self.location_manager.available_actions:
|
||||
ac = self.build_menu(ac)
|
||||
self.addAction(ac)
|
||||
self.added_actions.append(ac)
|
||||
elif what == 'Donate':
|
||||
self.addAction(self.donate_action)
|
||||
elif what in self.gui.iactions:
|
||||
action = self.gui.iactions[what]
|
||||
ac = self.build_menu(action.qaction)
|
||||
self.addAction(ac)
|
||||
self.added_actions.append(ac)
|
||||
|
||||
def build_menu(self, action):
|
||||
m = action.menu()
|
||||
ac = MenuAction(action, self)
|
||||
if m is None:
|
||||
m = QMenu()
|
||||
m.addAction(action)
|
||||
ac.setMenu(m)
|
||||
return ac
|
||||
|
||||
# }}}
|
||||
|
||||
class BaseToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, parent):
|
||||
QToolBar.__init__(self, parent)
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
self.setOrientation(Qt.Horizontal)
|
||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||
self.preferred_width = self.sizeHint().width()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
QToolBar.resizeEvent(self, ev)
|
||||
style = self.get_text_style()
|
||||
self.setToolButtonStyle(style)
|
||||
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
|
||||
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
|
||||
|
||||
def get_text_style(self):
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
s = gprefs['toolbar_icon_size']
|
||||
if s != 'off':
|
||||
p = gprefs['toolbar_text']
|
||||
if p == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
elif p == 'auto' and self.preferred_width > self.width()+35:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
return style
|
||||
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
# }}}
|
||||
|
||||
class ToolBar(BaseToolBar): # {{{
|
||||
|
||||
def __init__(self, donate, location_manager, child_bar, parent):
|
||||
BaseToolBar.__init__(self, parent)
|
||||
self.gui = parent
|
||||
self.child_bar = child_bar
|
||||
self.donate_button = donate
|
||||
self.apply_settings()
|
||||
|
||||
self.location_manager = location_manager
|
||||
self.location_manager.locations_changed.connect(self.build_bar)
|
||||
donate.setAutoRaise(True)
|
||||
donate.setCursor(Qt.PointingHandCursor)
|
||||
self.added_actions = []
|
||||
self.build_bar()
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
def apply_settings(self):
|
||||
sz = gprefs['toolbar_icon_size']
|
||||
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
|
||||
self.setIconSize(QSize(sz, sz))
|
||||
self.child_bar.setIconSize(QSize(sz, sz))
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
if sz > 0 and gprefs['toolbar_text'] == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
self.setToolButtonStyle(style)
|
||||
self.child_bar.setToolButtonStyle(style)
|
||||
self.donate_button.set_normal_icon_size(sz, sz)
|
||||
|
||||
def build_bar(self):
|
||||
self.showing_donate = False
|
||||
showing_device = self.location_manager.has_device
|
||||
mactions = '-device' if showing_device else ''
|
||||
mactions = gprefs['action-layout-toolbar'+mactions]
|
||||
cactions = gprefs['action-layout-toolbar-child']
|
||||
|
||||
show_main = len(mactions) > 0
|
||||
self.setVisible(show_main)
|
||||
show_child = len(cactions) > 0
|
||||
self.child_bar.setVisible(show_child)
|
||||
|
||||
for ac in self.added_actions:
|
||||
m = ac.menu()
|
||||
if m is not None:
|
||||
m.setVisible(False)
|
||||
|
||||
self.clear()
|
||||
self.child_bar.clear()
|
||||
self.added_actions = []
|
||||
|
||||
for bar, actions in ((self, mactions), (self.child_bar, cactions)):
|
||||
for what in actions:
|
||||
if what is None:
|
||||
bar.addSeparator()
|
||||
elif what == 'Location Manager':
|
||||
for ac in self.location_manager.available_actions:
|
||||
bar.addAction(ac)
|
||||
bar.added_actions.append(ac)
|
||||
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
|
||||
elif what == 'Donate':
|
||||
self.d_widget = QWidget()
|
||||
self.d_widget.setLayout(QVBoxLayout())
|
||||
self.d_widget.layout().addWidget(self.donate_button)
|
||||
if isosx:
|
||||
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
|
||||
self.d_widget.layout().setContentsMargins(0,0,0,0)
|
||||
self.d_widget.setContentsMargins(0,0,0,0)
|
||||
self.d_widget.filler = QLabel(u'\u00a0')
|
||||
self.d_widget.layout().addWidget(self.d_widget.filler)
|
||||
bar.addWidget(self.d_widget)
|
||||
self.showing_donate = True
|
||||
elif what in self.gui.iactions:
|
||||
action = self.gui.iactions[what]
|
||||
bar.addAction(action.qaction)
|
||||
self.added_actions.append(action.qaction)
|
||||
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
||||
self.preferred_width = self.sizeHint().width()
|
||||
self.child_bar.preferred_width = self.child_bar.sizeHint().width()
|
||||
|
||||
def setup_tool_button(self, bar, ac, menu_mode=None):
|
||||
ch = bar.widgetForAction(ac)
|
||||
if ch is None:
|
||||
ch = self.child_bar.widgetForAction(ac)
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
if ac.menu() is not None and menu_mode is not None:
|
||||
ch.setPopupMode(menu_mode)
|
||||
return ch
|
||||
|
||||
def database_changed(self, db):
|
||||
pass
|
||||
|
||||
#support drag&drop from/to library from/to reader/card
|
||||
def dragEnterEvent(self, event):
|
||||
md = event.mimeData()
|
||||
if md.hasFormat("application/calibre+from_library") or \
|
||||
md.hasFormat("application/calibre+from_device"):
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
allowed = False
|
||||
md = event.mimeData()
|
||||
#Drop is only allowed in the location manager widget's different from the selected one
|
||||
for ac in self.location_manager.available_actions:
|
||||
w = self.widgetForAction(ac)
|
||||
if w is not None:
|
||||
if ( md.hasFormat("application/calibre+from_library") or \
|
||||
md.hasFormat("application/calibre+from_device") ) and \
|
||||
w.geometry().contains(event.pos()) and \
|
||||
isinstance(w, QToolButton) and not w.isChecked():
|
||||
allowed = True
|
||||
break
|
||||
if allowed:
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dropEvent(self, event):
|
||||
data = event.mimeData()
|
||||
|
||||
mime = 'application/calibre+from_library'
|
||||
if data.hasFormat(mime):
|
||||
ids = list(map(int, str(data.data(mime)).split()))
|
||||
tgt = None
|
||||
for ac in self.location_manager.available_actions:
|
||||
w = self.widgetForAction(ac)
|
||||
if w is not None and w.geometry().contains(event.pos()):
|
||||
tgt = ac.calibre_name
|
||||
if tgt is not None:
|
||||
if tgt == 'main':
|
||||
tgt = None
|
||||
self.gui.sync_to_device(tgt, False, send_ids=ids)
|
||||
event.accept()
|
||||
|
||||
mime = 'application/calibre+from_device'
|
||||
if data.hasFormat(mime):
|
||||
paths = [unicode(u.toLocalFile()) for u in data.urls()]
|
||||
if paths:
|
||||
self.gui.iactions['Add Books'].add_books_from_device(
|
||||
self.gui.current_view(), paths=paths)
|
||||
event.accept()
|
||||
|
||||
# }}}
|
||||
|
||||
class MainWindowMixin(object): # {{{
|
||||
|
||||
@ -507,13 +257,13 @@ class MainWindowMixin(object): # {{{
|
||||
self.iactions['Fetch News'].init_scheduler(db)
|
||||
|
||||
self.search_bar = SearchBar(self)
|
||||
self.child_bar = BaseToolBar(self)
|
||||
self.tool_bar = ToolBar(self.donate_button,
|
||||
self.location_manager, self.child_bar, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||
self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
|
||||
self.menu_bar = MenuBar(self.location_manager, self)
|
||||
self.setMenuBar(self.menu_bar)
|
||||
self.bars_manager = BarsManager(self.donate_button,
|
||||
self.location_manager, self)
|
||||
for bar in self.bars_manager.main_bars:
|
||||
self.addToolBar(Qt.TopToolBarArea, bar)
|
||||
for bar in self.bars_manager.child_bars:
|
||||
self.addToolBar(Qt.BottomToolBarArea, bar)
|
||||
self.bars_manager.update_bars()
|
||||
self.setUnifiedTitleAndToolBarOnMac(True)
|
||||
|
||||
l = self.centralwidget.layout()
|
||||
|
@ -10,7 +10,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import textwrap
|
||||
|
||||
from PyQt4.Qt import (QWidget, QGridLayout, QGroupBox, QListView, Qt, QSpinBox,
|
||||
QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel)
|
||||
QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel, QVariant)
|
||||
|
||||
from calibre.gui2.preferences.metadata_sources import FieldsModel as FM
|
||||
|
||||
@ -95,9 +95,9 @@ class ConfigWidget(QWidget):
|
||||
widget.setChecked(bool(val))
|
||||
elif opt.type == 'choices':
|
||||
widget = QComboBox(self)
|
||||
for x in opt.choices:
|
||||
widget.addItem(x)
|
||||
idx = opt.choices.index(val)
|
||||
for key, label in opt.choices.iteritems():
|
||||
widget.addItem(label, QVariant(key))
|
||||
idx = widget.findData(QVariant(val))
|
||||
widget.setCurrentIndex(idx)
|
||||
widget.opt = opt
|
||||
widget.setToolTip(textwrap.fill(opt.desc))
|
||||
@ -124,7 +124,8 @@ class ConfigWidget(QWidget):
|
||||
elif isinstance(w, QCheckBox):
|
||||
val = w.isChecked()
|
||||
elif isinstance(w, QComboBox):
|
||||
val = unicode(w.currentText())
|
||||
idx = w.currentIndex()
|
||||
val = unicode(w.itemData(idx).toString())
|
||||
self.plugin.prefs[w.opt.name] = val
|
||||
|
||||
|
||||
|
@ -361,10 +361,9 @@ class Preferences(QMainWindow):
|
||||
self.gui.tags_view.recount()
|
||||
self.gui.create_device_menu()
|
||||
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
||||
self.gui.tool_bar.build_bar()
|
||||
self.gui.menu_bar.build_bar()
|
||||
self.gui.bars_manager.apply_settings()
|
||||
self.gui.bars_manager.update_bars()
|
||||
self.gui.build_context_menus()
|
||||
self.gui.tool_bar.apply_settings()
|
||||
|
||||
return QMainWindow.closeEvent(self, *args)
|
||||
|
||||
|
@ -317,6 +317,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
am.restore_defaults()
|
||||
self.changed_signal.emit()
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
gui.bars_manager.init_bars()
|
||||
gui.bars_manager.update_bars()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
|
89
src/calibre/gui2/store/archive_org_plugin.py
Normal file
89
src/calibre/gui2/store/archive_org_plugin.py
Normal file
@ -0,0 +1,89 @@
|
||||
# -*- 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
|
@ -26,14 +26,9 @@ class BeWriteStore(BasicStoreConfig, StorePlugin):
|
||||
url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT'
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
if detail_item:
|
||||
url = url + detail_item
|
||||
open_url(QUrl(url_slash_cleaner(url)))
|
||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
||||
else:
|
||||
detail_url = None
|
||||
if detail_item:
|
||||
detail_url = url + detail_item
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_url)
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
93
src/calibre/gui2/store/google_books_plugin.py
Normal file
93
src/calibre/gui2/store/google_books_plugin.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- 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 GoogleBooksStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
url = 'http://books.google.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://www.google.com/search?tbm=bks&q=' + 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('//ol[@id="rso"]/li'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//h3/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
title = ''.join(data.xpath('.//h3/a//text()'))
|
||||
authors = data.xpath('.//span[@class="gl"]//a//text()')
|
||||
if authors[-1].strip().lower() == 'preview':
|
||||
authors = authors[:-1]
|
||||
else:
|
||||
continue
|
||||
author = ', '.join(authors)
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.detail_item = id.strip()
|
||||
s.drm = SearchResult.DRM_UNKNOWN
|
||||
|
||||
yield s
|
||||
|
||||
def get_details(self, search_result, timeout):
|
||||
br = browser()
|
||||
with closing(br.open(search_result.detail_item, timeout=timeout)) as nf:
|
||||
doc = html.fromstring(nf.read())
|
||||
|
||||
search_result.cover_url = ''.join(doc.xpath('//div[@class="sidebarcover"]//img/@src'))
|
||||
|
||||
# Try to get the set price.
|
||||
price = ''.join(doc.xpath('//div[@class="buy-price-container"]/span[contains(@class, "buy-price")]/text()'))
|
||||
# Try to get the price inside of a buy button.
|
||||
if not price.strip():
|
||||
price = ''.join(doc.xpath('//div[@class="buy-container"]/a/text()'))
|
||||
price = price.split('-')[-1]
|
||||
# No price set for this book.
|
||||
if not price.strip():
|
||||
price = '$0.00'
|
||||
search_result.price = price.strip()
|
||||
|
||||
search_result.formats = ', '.join(doc.xpath('//div[contains(@class, "download-panel-div")]//a/text()')).upper()
|
||||
if not search_result.formats:
|
||||
search_result.formats = _('Unknown')
|
||||
|
||||
return True
|
||||
|
@ -20,6 +20,7 @@ class MobeReadStoreDialog(QDialog, Ui_Dialog):
|
||||
self.setupUi(self)
|
||||
|
||||
self.plugin = plugin
|
||||
self.search_query.initialize('store_mobileread_search')
|
||||
|
||||
self.adv_search_button.setIcon(QIcon(I('search.png')))
|
||||
|
||||
|
@ -34,7 +34,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search_query"/>
|
||||
<widget class="HistoryLineEdit" name="search_query">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search_button">
|
||||
@ -107,6 +114,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>HistoryLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
86
src/calibre/gui2/store/nexto_plugin.py
Normal file
86
src/calibre/gui2/store/nexto_plugin.py
Normal file
@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
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 NextoStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
pid = '155711'
|
||||
|
||||
url = 'http://www.nexto.pl/ebooki_c1015.xml'
|
||||
detail_url = None
|
||||
|
||||
if detail_item:
|
||||
book_id = re.search(r'p[0-9]*\.xml\Z', detail_item)
|
||||
book_id = book_id.group(0).replace('.xml','').replace('p','')
|
||||
if book_id:
|
||||
detail_url = 'http://www.nexto.pl/rf/pr?p=' + book_id + '&pid=' + pid
|
||||
|
||||
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.nexto.pl/szukaj.xml?search-clause=' + urllib.quote_plus(query.encode('utf-8')) + '&scid=1015'
|
||||
|
||||
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="productslist"]/li'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="cover_container"]/a[1]/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
price = ''.join(data.xpath('.//strong[@class="nprice"]/text()'))
|
||||
|
||||
cover_url = ''.join(data.xpath('.//img[@class="cover"]/@src'))
|
||||
title = ''.join(data.xpath('.//a[@class="title"]/text()'))
|
||||
formats = ', '.join(data.xpath('.//ul[@class="formats_available"]/li//b/text()'))
|
||||
DrmFree = re.search(r'bez.DRM', formats)
|
||||
formats = re.sub(r'\(.+\)', '', formats)
|
||||
|
||||
author = ''
|
||||
with closing(br.open('http://www.nexto.pl/' + id.strip(), timeout=timeout/4)) as nf:
|
||||
idata = html.fromstring(nf.read())
|
||||
author = ''.join(idata.xpath('//div[@class="basic_data"]/p[1]/b/a/text()'))
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = cover_url
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price
|
||||
s.detail_item = id.strip()
|
||||
s.drm = SearchResult.DRM_UNLOCKED if DrmFree else SearchResult.DRM_LOCKED
|
||||
s.formats = formats.upper().strip()
|
||||
|
||||
yield s
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import re
|
||||
from random import shuffle
|
||||
|
||||
from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon)
|
||||
from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon, QWidget)
|
||||
|
||||
from calibre.gui2 import JSONConfig, info_dialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
@ -29,6 +29,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
|
||||
self.config = JSONConfig('store/search')
|
||||
|
||||
self.search_edit.initialize('store_search_search')
|
||||
|
||||
# We keep a cache of store plugins and reference them by name.
|
||||
self.store_plugins = istores
|
||||
self.search_pool = SearchThreadPool(4)
|
||||
@ -45,14 +47,16 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
# Add check boxes for each store so the user
|
||||
# can disable searching specific stores on a
|
||||
# per search basis.
|
||||
stores_check_widget = QWidget()
|
||||
stores_group_layout = QVBoxLayout()
|
||||
self.stores_group.setLayout(stores_group_layout)
|
||||
stores_check_widget.setLayout(stores_group_layout)
|
||||
for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()):
|
||||
cbox = QCheckBox(x)
|
||||
cbox.setChecked(True)
|
||||
cbox.setChecked(False)
|
||||
stores_group_layout.addWidget(cbox)
|
||||
setattr(self, 'store_check_' + x, cbox)
|
||||
stores_group_layout.addStretch()
|
||||
self.stores_group.setWidget(stores_check_widget)
|
||||
|
||||
# Set the search query
|
||||
self.search_edit.setText(query)
|
||||
|
@ -38,7 +38,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search_edit"/>
|
||||
<widget class="HistoryLineEdit" name="search_edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
@ -201,6 +208,11 @@
|
||||
<extends>QTreeView</extends>
|
||||
<header>results_view.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>HistoryLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
|
@ -44,6 +44,7 @@ class WizardsTowerBooksStore(BasicStoreConfig, StorePlugin):
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
if 'search.html' in f.geturl():
|
||||
for data in doc.xpath('//table[@class="gridp"]//td'):
|
||||
if counter <= 0:
|
||||
break
|
||||
@ -75,9 +76,38 @@ class WizardsTowerBooksStore(BasicStoreConfig, StorePlugin):
|
||||
s.detail_item = id.strip()
|
||||
s.drm = SearchResult.DRM_UNLOCKED
|
||||
|
||||
yield s
|
||||
# Exact match brought us to the books detail page.
|
||||
else:
|
||||
s = SearchResult()
|
||||
|
||||
cover_url = ''.join(doc.xpath('//div[@id="image"]/a/img[@title="Zoom"]/@src')).strip()
|
||||
s.cover_url = url_slash_cleaner(self.url + cover_url.strip())
|
||||
|
||||
s.title = ''.join(doc.xpath('//form[@name="details"]/h1/text()')).strip()
|
||||
|
||||
authors = doc.xpath('//p[contains(., "Author:")]//text()')
|
||||
author_index = None
|
||||
for i, a in enumerate(authors):
|
||||
if 'author' in a.lower():
|
||||
author_index = i + 1
|
||||
break
|
||||
if author_index is not None and len(authors) > author_index:
|
||||
a = authors[author_index]
|
||||
a = a.replace(u'\xa0', '')
|
||||
s.author = a.strip()
|
||||
|
||||
s.price = ''.join(doc.xpath('//span[@id="price_selling"]//text()')).strip()
|
||||
s.detail_item = f.geturl().replace(self.url, '').strip()
|
||||
s.formats = ', '.join(doc.xpath('//select[@id="N1_"]//option//text()'))
|
||||
s.drm = SearchResult.DRM_UNLOCKED
|
||||
|
||||
yield s
|
||||
|
||||
def get_details(self, search_result, timeout):
|
||||
if search_result.formats:
|
||||
return False
|
||||
|
||||
br = browser()
|
||||
with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf:
|
||||
idata = html.fromstring(nf.read())
|
||||
|
@ -1263,7 +1263,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
d['last'] = data[key][cat_len-1]
|
||||
name = eval_formatter.safe_format(collapse_template,
|
||||
d, 'TAG_VIEW', None)
|
||||
self.beginInsertRows(category_index, 999999, 1) #len(data[key])-1)
|
||||
self.beginInsertRows(category_index, 999998, 999999) #len(data[key])-1)
|
||||
sub_cat = TagTreeItem(parent=category, data = name,
|
||||
tooltip = None, temporary=True,
|
||||
category_icon = category_node.icon,
|
||||
@ -1296,7 +1296,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
|
||||
key not in self.db.prefs.get('categories_using_hierarchy', []) or
|
||||
len(components) == 1):
|
||||
self.beginInsertRows(category_index, 999999, 1)
|
||||
self.beginInsertRows(category_index, 999998, 999999)
|
||||
n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
|
||||
icon_map=self.icon_state_map)
|
||||
if tag.id_set is not None:
|
||||
@ -1332,7 +1332,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
t.is_hierarchical = \
|
||||
'5state' if t.category != 'search' else '3state'
|
||||
t.name = comp
|
||||
self.beginInsertRows(category_index, 999999, 1)
|
||||
self.beginInsertRows(category_index, 999998, 999999)
|
||||
node_parent = TagTreeItem(parent=node_parent, data=t,
|
||||
tooltip=tt, icon_map=self.icon_state_map)
|
||||
child_map[(comp,tag.category)] = node_parent
|
||||
|
@ -288,8 +288,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.db_images.reset()
|
||||
|
||||
self.library_view.model().count_changed()
|
||||
self.tool_bar.database_changed(self.library_view.model().db)
|
||||
self.library_view.model().database_changed.connect(self.tool_bar.database_changed,
|
||||
self.bars_manager.database_changed(self.library_view.model().db)
|
||||
self.library_view.model().database_changed.connect(self.bars_manager.database_changed,
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
########################### Tags Browser ##############################
|
||||
@ -324,7 +324,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
|
||||
self.read_settings()
|
||||
self.finalize_layout()
|
||||
if self.tool_bar.showing_donate:
|
||||
if self.bars_manager.showing_donate:
|
||||
self.donate_button.start_animation()
|
||||
self.set_window_title()
|
||||
|
||||
|
@ -22,7 +22,7 @@ It can convert every input format in the following list, to every output format.
|
||||
|
||||
*Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ
|
||||
|
||||
*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, SNB, TCR, TXT, TXTZ
|
||||
*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, RTF, SNB, TCR, TXT, TXTZ
|
||||
|
||||
.. note ::
|
||||
|
||||
|
@ -50,6 +50,12 @@ PARALLEL_FUNCS = {
|
||||
|
||||
'save_book' :
|
||||
('calibre.ebooks.metadata.worker', 'save_book', 'notification'),
|
||||
|
||||
'arbitrary' :
|
||||
('calibre.utils.ipc.worker', 'arbitrary', None),
|
||||
|
||||
'arbitrary_n' :
|
||||
('calibre.utils.ipc.worker', 'arbitrary', 'notification'),
|
||||
}
|
||||
|
||||
class Progress(Thread):
|
||||
@ -73,7 +79,55 @@ class Progress(Thread):
|
||||
except:
|
||||
break
|
||||
|
||||
def arbitrary(module_name, func_name, args, kwargs={}):
|
||||
'''
|
||||
An entry point that allows arbitrary functions to be run in a parallel
|
||||
process. useful for plugin developers that want to run jobs in a parallel
|
||||
process.
|
||||
|
||||
To use this entry point, simply create a ParallelJob with the module and
|
||||
function names for the real entry point.
|
||||
|
||||
Remember that args and kwargs must be serialized so only use basic types
|
||||
for them.
|
||||
|
||||
To use this, you will do something like
|
||||
|
||||
from calibre.gui2 import Dispatcher
|
||||
gui.job_manager.run_job(Dispatcher(job_done), 'arbitrary',
|
||||
args=('calibre_plugins.myplugin.worker', 'do_work',
|
||||
('arg1' 'arg2', 'arg3')),
|
||||
description='Change the world')
|
||||
|
||||
The function job_done will be called on completion, see the code in
|
||||
gui2.actions.catalog for an example of using run_job and Dispatcher.
|
||||
|
||||
:param module_name: The fully qualified name of the module that contains
|
||||
the actual function to be run. For example:
|
||||
calibre_plugins.myplugin.worker
|
||||
:param func_name: The name of the function to be run.
|
||||
:param name: A list (or tuple) of arguments that will be passed to the
|
||||
function ``func_name``
|
||||
:param kwargs: A dictionary of keyword arguments to pass to func_name
|
||||
'''
|
||||
module = importlib.import_module(module_name)
|
||||
func = getattr(module, func_name)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def arbitrary_n(module_name, func_name, args, kwargs={},
|
||||
notification=lambda x, y: y):
|
||||
'''
|
||||
Same as :func:`arbitrary` above, except that func_name must support a
|
||||
keyword argument "notification". This will be a function that accepts two
|
||||
arguments. func_name should call it periodically with progress information.
|
||||
The first argument is a float between 0 and 1 that represent percent
|
||||
completed and the second is a string with a message (it can be an empty
|
||||
string).
|
||||
'''
|
||||
module = importlib.import_module(module_name)
|
||||
func = getattr(module, func_name)
|
||||
kwargs['notification'] = notification
|
||||
return func(*args, **kwargs)
|
||||
|
||||
def get_func(name):
|
||||
module, func, notification = PARALLEL_FUNCS[name]
|
||||
|
@ -10,7 +10,6 @@ License: http://www.opensource.org/licenses/mit-license.php
|
||||
import re
|
||||
|
||||
from calibre.utils.icu import capitalize
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
__all__ = ['titlecase']
|
||||
__version__ = '0.5'
|
||||
@ -31,6 +30,17 @@ ALL_CAPS = re.compile(r'^[A-Z\s%s]+$' % PUNCT)
|
||||
UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$")
|
||||
MAC_MC = re.compile(r"^([Mm]a?c)(.+)")
|
||||
|
||||
|
||||
_lang = None
|
||||
|
||||
def lang():
|
||||
global _lang
|
||||
if _lang is None:
|
||||
from calibre.utils.localization import get_lang
|
||||
_lang = get_lang().lower()
|
||||
return _lang
|
||||
|
||||
|
||||
def titlecase(text):
|
||||
|
||||
"""
|
||||
@ -68,7 +78,7 @@ def titlecase(text):
|
||||
line.append(icu_lower(word))
|
||||
continue
|
||||
|
||||
if prefs['language'].lower().startswith('en'):
|
||||
if lang().startswith('en'):
|
||||
match = MAC_MC.match(word)
|
||||
if match and not match.group(2)[:3] in ('hin', 'ht'):
|
||||
line.append("%s%s" % (capitalize(match.group(1)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user