mirror of
https://github.com/wassname/FanFicFare.git
synced 2026-09-15 11:36:30 +08:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cfaff9d42 | ||
|
|
793c65495f | ||
|
|
efb908d438 | ||
|
|
6ea015b111 | ||
|
|
eab986a07a | ||
|
|
89b86545e0 | ||
|
|
3543eecfcc | ||
|
|
dc2370fed9 | ||
|
|
f83d9467fc | ||
|
|
a82a40a221 |
@@ -1,6 +1,6 @@
|
||||
# ffd-retief-hrd fanfictiondownloader
|
||||
application: fanfictiondownloader
|
||||
version: 4-4-21
|
||||
version: 4-4-22
|
||||
runtime: python27
|
||||
api_version: 1
|
||||
threadsafe: true
|
||||
|
||||
@@ -27,7 +27,7 @@ class FanFictionDownLoaderBase(InterfaceActionBase):
|
||||
description = 'UI plugin to download FanFiction stories from various sites.'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
author = 'Jim Miller'
|
||||
version = (1, 6, 2)
|
||||
version = (1, 6, 4)
|
||||
minimum_calibre_version = (0, 8, 57)
|
||||
|
||||
#: This field defines the GUI plugin class that contains all the code
|
||||
|
||||
@@ -628,27 +628,27 @@ class CountPagesTab(QWidget):
|
||||
self.l.addSpacing(5)
|
||||
|
||||
# 'PageCount', 'WordCount', 'FleschReading', 'FleschGrade', 'GunningFog'
|
||||
self.pagecount = QCheckBox('PageCount',self)
|
||||
self.pagecount = QCheckBox('Page Count',self)
|
||||
self.pagecount.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.pagecount.setChecked('PageCount' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.pagecount)
|
||||
|
||||
self.wordcount = QCheckBox('WordCount',self)
|
||||
self.wordcount = QCheckBox('Word Count',self)
|
||||
self.wordcount.setToolTip('Which column and algorithm to use are configured in Count Words.\nWill overwrite word count from FFDL metadata if set to update the same custom column.')
|
||||
self.wordcount.setChecked('WordCount' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.wordcount)
|
||||
|
||||
self.fleschreading = QCheckBox('FleschReading',self)
|
||||
self.fleschreading = QCheckBox('Flesch Reading Ease',self)
|
||||
self.fleschreading.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.fleschreading.setChecked('FleschReading' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.fleschreading)
|
||||
|
||||
self.fleschgrade = QCheckBox('Fleschgrade',self)
|
||||
self.fleschgrade = QCheckBox('Flesch-Kincaid Grade Level',self)
|
||||
self.fleschgrade.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.fleschgrade.setChecked('Fleschgrade' in prefs['countpagesstats'])
|
||||
self.fleschgrade.setChecked('FleschGrade' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.fleschgrade)
|
||||
|
||||
self.gunningfog = QCheckBox('GunningFog',self)
|
||||
self.gunningfog = QCheckBox('Gunning Fog Index',self)
|
||||
self.gunningfog.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.gunningfog.setChecked('GunningFog' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.gunningfog)
|
||||
|
||||
@@ -172,15 +172,17 @@ class FakeLineEdit():
|
||||
def text(self):
|
||||
pass
|
||||
|
||||
class CollectURLDialog(QDialog):
|
||||
class CollectURLDialog(SizePersistedDialog):
|
||||
'''
|
||||
Collect single url for get urls.
|
||||
'''
|
||||
def __init__(self, gui, title):
|
||||
QDialog.__init__(self, gui)
|
||||
def __init__(self, gui, title, url_text):
|
||||
SizePersistedDialog.__init__(self, gui, 'FanFictionDownLoader plugin:get story urls')
|
||||
self.gui = gui
|
||||
self.status=False
|
||||
|
||||
self.setMinimumWidth(300)
|
||||
|
||||
self.l = QGridLayout()
|
||||
self.setLayout(self.l)
|
||||
|
||||
@@ -189,6 +191,7 @@ class CollectURLDialog(QDialog):
|
||||
|
||||
self.l.addWidget(QLabel("URL:"),1,0)
|
||||
self.url = QLineEdit(self)
|
||||
self.url.setText(url_text)
|
||||
self.l.addWidget(self.url,1,1)
|
||||
|
||||
self.ok_button = QPushButton('OK', self)
|
||||
@@ -199,15 +202,16 @@ class CollectURLDialog(QDialog):
|
||||
self.cancel_button.clicked.connect(self.cancel)
|
||||
self.l.addWidget(self.cancel_button,2,1)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
# restore saved size.
|
||||
self.resize_dialog()
|
||||
|
||||
def ok(self):
|
||||
self.status=True
|
||||
self.hide()
|
||||
self.accept()
|
||||
|
||||
def cancel(self):
|
||||
self.status=False
|
||||
self.hide()
|
||||
self.reject()
|
||||
|
||||
class UserPassDialog(QDialog):
|
||||
'''
|
||||
|
||||
@@ -238,13 +238,23 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
self._update_reading_lists(self.gui.library_view.get_selected_ids(),add)
|
||||
|
||||
def get_urls_from_page(self):
|
||||
d = CollectURLDialog(self.gui,"Get Story URLs from Web Page")
|
||||
|
||||
if prefs['urlsfromclip']:
|
||||
try:
|
||||
urltxt = self.get_urls_clip(storyurls=False)[0]
|
||||
except:
|
||||
urltxt = ""
|
||||
|
||||
d = CollectURLDialog(self.gui,"Get Story URLs from Web Page",urltxt)
|
||||
d.exec_()
|
||||
if not d.status:
|
||||
return
|
||||
print("URL:%s"%d.url.text())
|
||||
print("get_urls_from_page URL:%s"%d.url.text())
|
||||
|
||||
url_list = get_urls_from_page("%s"%d.url.text())
|
||||
ffdlconfig = SafeConfigParser()
|
||||
ffdlconfig.readfp(StringIO(get_resources("plugin-defaults.ini")))
|
||||
ffdlconfig.readfp(StringIO(prefs['personal.ini']))
|
||||
url_list = get_urls_from_page("%s"%d.url.text(),ffdlconfig)
|
||||
|
||||
if url_list:
|
||||
d = ViewLog(_("List of URLs"),"\n".join(url_list),parent=self.gui)
|
||||
@@ -361,13 +371,14 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
options['version'] = self.version
|
||||
print(self.version)
|
||||
self.start_downloads( options, update_books )
|
||||
|
||||
def get_urls_clip(self):
|
||||
|
||||
def get_urls_clip(self,storyurls=True):
|
||||
url_list = []
|
||||
if prefs['urlsfromclip']:
|
||||
for url in unicode(QApplication.instance().clipboard().text()).split():
|
||||
if( self._is_good_downloader_url(url) ):
|
||||
if not storyurls or self._is_good_downloader_url(url):
|
||||
url_list.append(url)
|
||||
|
||||
return url_list
|
||||
|
||||
def apply_settings(self):
|
||||
@@ -766,20 +777,20 @@ make_firstimage_cover:true
|
||||
<p>Proceed with updating your library?</p>
|
||||
'''%(len(good_list),len(bad_list))
|
||||
|
||||
htmllog='<html><body><table border="1"><tr><th>Status</th><th>Title</th><th>Author</th><th>URL</th><th>Comment</th></tr>'
|
||||
htmllog='<html><body><table border="1"><tr><th>Status</th><th>Title</th><th>Author</th><th>Comment</th><th>URL</th></tr>'
|
||||
for book in good_list:
|
||||
if 'status' in book:
|
||||
status = book['status']
|
||||
else:
|
||||
status = 'Good'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['url'],book['comment']]) + '</td></tr>'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['comment'],book['url']]) + '</td></tr>'
|
||||
|
||||
for book in bad_list:
|
||||
if 'status' in book:
|
||||
status = book['status']
|
||||
else:
|
||||
status = 'Bad'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['url'],book['comment']]) + '</td></tr>'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['comment'],book['url']]) + '</td></tr>'
|
||||
|
||||
htmllog = htmllog + '</table></body></html>'
|
||||
|
||||
|
||||
@@ -154,11 +154,13 @@ def do_download_for_worker(book,options):
|
||||
# update now handled by pre-populating the old images and
|
||||
# chapters in the adapter rather than merging epubs.
|
||||
urlchaptercount = int(story.getMetadata('numChapters'))
|
||||
(url,chaptercount,
|
||||
(url,
|
||||
chaptercount,
|
||||
adapter.oldchapters,
|
||||
adapter.oldimgs,
|
||||
adapter.oldcover,
|
||||
adapter.calibrebookmark) = get_update_data(book['epub_for_update'])
|
||||
adapter.calibrebookmark,
|
||||
adapter.logfile) = get_update_data(book['epub_for_update'])
|
||||
|
||||
print("Do update - epub(%d) vs url(%d)" % (chaptercount, urlchaptercount))
|
||||
print("write to %s"%outfile)
|
||||
|
||||
+16
-1
@@ -197,9 +197,24 @@ windows_eol: true
|
||||
zip_output: false
|
||||
|
||||
## epub carries the TOC in metadata.
|
||||
## mobi generated from epub will have a TOC at the end.
|
||||
## mobi generated from epub by calibre will have a TOC at the end.
|
||||
include_tocpage: false
|
||||
|
||||
## include a Update Log page before the story text. If included, the
|
||||
## log will be updated each time the epub is an all the metadata
|
||||
## fields that have changed since the last update (typically
|
||||
## dateUpdated,numChapters,numWords at a minimum) will be shown.
|
||||
## Great for tracking when chapters came out and when the description,
|
||||
## etc changed.
|
||||
include_logpage: false
|
||||
|
||||
## items to include in the log page Empty metadata entries, or those
|
||||
## that haven't changed since the last update, will *not* appear, even
|
||||
## if in the list. You can include extra text or HTML that will be
|
||||
## included as-is in each log entry. Eg: logpage_entries: ...,<br />,
|
||||
## summary,<br />,...
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,title,author,description,category,genre,rating,warnings
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
|
||||
+5
-3
@@ -128,7 +128,7 @@ def main():
|
||||
config.set("overrides",var,val)
|
||||
|
||||
if options.list:
|
||||
retlist = get_urls_from_page(args[0])
|
||||
retlist = get_urls_from_page(args[0], config)
|
||||
print "\n".join(retlist)
|
||||
|
||||
return
|
||||
@@ -193,11 +193,13 @@ def main():
|
||||
# update now handled by pre-populating the old
|
||||
# images and chapters in the adapter rather than
|
||||
# merging epubs.
|
||||
(url,chaptercount,
|
||||
(url,
|
||||
chaptercount,
|
||||
adapter.oldchapters,
|
||||
adapter.oldimgs,
|
||||
adapter.oldcover,
|
||||
adapter.calibrebookmark) = get_update_data(args[0])
|
||||
adapter.calibrebookmark,
|
||||
adapter.logfile) = get_update_data(args[0])
|
||||
|
||||
writeStory(config,adapter,"epub")
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ def getAdapter(config,url,fileform=None):
|
||||
if( domain != parsedUrl.netloc ):
|
||||
fixedurl = fixedurl.replace(parsedUrl.netloc,domain)
|
||||
|
||||
logging.debug("site:"+domain)
|
||||
logging.debug("trying url:"+url)
|
||||
cls = getClassFor(domain)
|
||||
if not cls and domain.startswith("www."):
|
||||
domain = domain.replace("www.","")
|
||||
|
||||
@@ -50,8 +50,17 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/works/'+self.story.getMetadata('storyId'))
|
||||
# get storyId from url--url validation guarantees query correct
|
||||
m = re.match(self.getSiteURLPattern(),url)
|
||||
if m:
|
||||
self.story.setMetadata('storyId',m.group('id'))
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/works/'+self.story.getMetadata('storyId'))
|
||||
else:
|
||||
raise exceptions.InvalidStoryURL(url,
|
||||
self.getSiteDomain(),
|
||||
self.getSiteExampleURLs())
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ao3')
|
||||
@@ -70,10 +79,11 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
return ['www.archiveofourown.org','archiveofourown.org']
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/works/123456"
|
||||
return "http://"+self.getSiteDomain()+"/works/123456 http://"+self.getSiteDomain()+"/collections/Some_Archive/works/123456"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://")+"(www\.)?"+re.escape(self.getSiteDomain()+"/works/")+r"\d+(/chapters/\d+)?/?$"
|
||||
# http://archiveofourown.org/collections/Smallville_Slash_Archive/works/159770
|
||||
return re.escape("http://")+"(www.)?"+re.escape(self.getSiteDomain())+r"(/collections/[^/]+)?/works/(?P<id>\d+)(/chapters/\d+)?/?$"
|
||||
|
||||
## Login
|
||||
def needToLoginCheck(self, data):
|
||||
|
||||
@@ -122,7 +122,7 @@ class NHAMagicalWorldsUsAdapter(BaseSiteAdapter):
|
||||
|
||||
|
||||
# Find the chapters:
|
||||
chapters=soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+'&chapter=\d$'))
|
||||
chapters=soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+'&chapter=\d+$'))
|
||||
if len(chapters) == 0:
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
|
||||
@@ -140,25 +140,25 @@ Some more longer description. "I suck at summaries!" "Better than it sounds!"
|
||||
('Chapter 3, Over Cinnabar',self.url+"&chapter=4"),
|
||||
('Chapter 4',self.url+"&chapter=5"),
|
||||
('Chapter 5',self.url+"&chapter=6"),
|
||||
('Chapter 6',self.url+"&chapter=6"),
|
||||
# ('Chapter 7',self.url+"&chapter=6"),
|
||||
# ('Chapter 8',self.url+"&chapter=6"),
|
||||
# ('Chapter 9',self.url+"&chapter=6"),
|
||||
# ('Chapter 0',self.url+"&chapter=6"),
|
||||
# ('Chapter a',self.url+"&chapter=6"),
|
||||
# ('Chapter b',self.url+"&chapter=6"),
|
||||
# ('Chapter c',self.url+"&chapter=6"),
|
||||
# ('Chapter d',self.url+"&chapter=6"),
|
||||
# ('Chapter e',self.url+"&chapter=6"),
|
||||
# ('Chapter f',self.url+"&chapter=6"),
|
||||
# ('Chapter g',self.url+"&chapter=6"),
|
||||
# ('Chapter h',self.url+"&chapter=6"),
|
||||
# ('Chapter i',self.url+"&chapter=6"),
|
||||
# ('Chapter j',self.url+"&chapter=6"),
|
||||
# ('Chapter k',self.url+"&chapter=6"),
|
||||
# ('Chapter l',self.url+"&chapter=6"),
|
||||
# ('Chapter m',self.url+"&chapter=6"),
|
||||
# ('Chapter n',self.url+"&chapter=6"),
|
||||
#('Chapter 6',self.url+"&chapter=7"),
|
||||
#('Chapter 7',self.url+"&chapter=8"),
|
||||
#('Chapter 8',self.url+"&chapter=9"),
|
||||
#('Chapter 9',self.url+"&chapter=0"),
|
||||
#('Chapter 0',self.url+"&chapter=a"),
|
||||
#('Chapter a',self.url+"&chapter=b"),
|
||||
#('Chapter b',self.url+"&chapter=c"),
|
||||
#('Chapter c',self.url+"&chapter=d"),
|
||||
#('Chapter d',self.url+"&chapter=e"),
|
||||
#('Chapter e',self.url+"&chapter=f"),
|
||||
#('Chapter f',self.url+"&chapter=g"),
|
||||
#('Chapter g',self.url+"&chapter=h"),
|
||||
#('Chapter h',self.url+"&chapter=i"),
|
||||
#('Chapter i',self.url+"&chapter=j"),
|
||||
#('Chapter j',self.url+"&chapter=k"),
|
||||
#('Chapter k',self.url+"&chapter=l"),
|
||||
#('Chapter l',self.url+"&chapter=m"),
|
||||
#('Chapter m',self.url+"&chapter=n"),
|
||||
#('Chapter n',self.url+"&chapter=o"),
|
||||
]
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
|
||||
@@ -141,7 +141,6 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
# because for some reason, this works while simple 'print data' errors on ascii conversion.
|
||||
# loopdata = data
|
||||
# chklen=5000
|
||||
@@ -151,6 +150,7 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
# logging.info("loopdata: %s" % loopdata[:chklen])
|
||||
# loopdata = loopdata[chklen:]
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
soup = bs.BeautifulSoup(data)
|
||||
|
||||
# Now go hunting for all the meta data and the chapter list.
|
||||
@@ -191,9 +191,11 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
svalue = ""
|
||||
while not defaultGetattr(value,'class') == 'label':
|
||||
while value and not defaultGetattr(value,'class') == 'label':
|
||||
svalue += str(value)
|
||||
value = value.nextSibling
|
||||
# sometimes poorly formated desc (<p> w/o </p>) leads
|
||||
# to all labels being included.
|
||||
self.setDescription(url,svalue)
|
||||
#self.story.setMetadata('description',stripHTML(svalue))
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ class BaseSiteAdapter(Configurable):
|
||||
self.oldimgs = None
|
||||
self.oldcover = None # (data of existing cover html, data of existing cover image)
|
||||
self.calibrebookmark = None
|
||||
self.logfile = None
|
||||
## order of preference for decoding.
|
||||
self.decode = ["utf8",
|
||||
"Windows-1252"] # 1252 is a superset of
|
||||
@@ -229,6 +230,8 @@ class BaseSiteAdapter(Configurable):
|
||||
# cheesy way to carry calibre bookmark file forward across update.
|
||||
if self.calibrebookmark:
|
||||
self.story.calibrebookmark = self.calibrebookmark
|
||||
if self.logfile:
|
||||
self.story.logfile = self.logfile
|
||||
|
||||
return self.story
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ def get_update_data(inputio,
|
||||
|
||||
oldcover = None
|
||||
calibrebookmark = None
|
||||
logfile = None
|
||||
# Looking for pre-existing cover.
|
||||
for item in contentdom.getElementsByTagName("reference"):
|
||||
if item.getAttribute("type") == "cover":
|
||||
@@ -96,6 +97,11 @@ def get_update_data(inputio,
|
||||
if( item.getAttribute("media-type") == "application/xhtml+xml" ):
|
||||
href=relpath+item.getAttribute("href")
|
||||
#print("---- item href:%s path part: %s"%(href,get_path_part(href)))
|
||||
if re.match(r'.*/log_page\.x?html',href):
|
||||
try:
|
||||
logfile = epub.read(href).decode("utf-8")
|
||||
except:
|
||||
pass # corner case I bumped into while testing.
|
||||
if re.match(r'.*/(file|chapter)\d+\.x?html',href):
|
||||
if getsoups:
|
||||
soup = bs.BeautifulSoup(epub.read(href).decode("utf-8"))
|
||||
@@ -136,7 +142,7 @@ def get_update_data(inputio,
|
||||
|
||||
for k in images.keys():
|
||||
print("\tlongdesc:%s\n\tData len:%s\n"%(k,len(images[k])))
|
||||
return (source,filecount,soups,images,oldcover,calibrebookmark)
|
||||
return (source,filecount,soups,images,oldcover,calibrebookmark,logfile)
|
||||
|
||||
def get_path_part(n):
|
||||
relpath = os.path.dirname(n)
|
||||
|
||||
@@ -25,14 +25,38 @@ from gziphttp import GZipProcessor
|
||||
|
||||
import adapters
|
||||
|
||||
def get_urls_from_page(url):
|
||||
|
||||
opener = u2.build_opener(u2.HTTPCookieProcessor(),GZipProcessor())
|
||||
soup = BeautifulSoup(opener.open(url).read())
|
||||
|
||||
def get_urls_from_page(url,config=None):
|
||||
|
||||
normalized = set() # normalized url
|
||||
retlist = [] # orig urls.
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
if not config:
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
|
||||
data = None
|
||||
|
||||
# special stuff to log into archiveofourown.org, if possible.
|
||||
# Unlike most that show the links to 'adult' stories, but protect
|
||||
# them, AO3 doesn't even show them if not logged in. Only works
|
||||
# with saved user/pass--not going to prompt for list.
|
||||
if 'archiveofourown.org' in url:
|
||||
ao3adapter = adapters.getAdapter(config,"http://www.archiveofourown.org/works/0","EPUB")
|
||||
if ao3adapter.getConfig("username"):
|
||||
if ao3adapter.getConfig("is_adult"):
|
||||
addurl = "?view_adult=true"
|
||||
else:
|
||||
addurl=""
|
||||
# just to get an authenticity_token.
|
||||
data = ao3adapter._fetchUrl(url+addurl)
|
||||
# login the session.
|
||||
ao3adapter.performLogin(url,data)
|
||||
# get the list page with logged in session.
|
||||
data = ao3adapter._fetchUrl(url)
|
||||
|
||||
if not data:
|
||||
opener = u2.build_opener(u2.HTTPCookieProcessor(),GZipProcessor())
|
||||
data = opener.open(url).read()
|
||||
|
||||
soup = BeautifulSoup(data)
|
||||
|
||||
for a in soup.findAll('a'):
|
||||
if a.has_key('href'):
|
||||
|
||||
@@ -201,6 +201,7 @@ class Story:
|
||||
self.cover=None # *href* of new cover image--need to create html.
|
||||
self.oldcover=None # (oldcoverhtmlhref,oldcoverhtmltype,oldcoverhtmldata,oldcoverimghref,oldcoverimgtype,oldcoverimgdata)
|
||||
self.calibrebookmark=None # cheesy way to carry calibre bookmark file forward across update.
|
||||
self.logfile=None # cheesy way to carry log file forward across update.
|
||||
|
||||
def setMetadata(self, key, value):
|
||||
## still keeps < < and &
|
||||
@@ -230,6 +231,8 @@ class Story:
|
||||
if value:
|
||||
if key == "numWords":
|
||||
value = commaGroups(value)
|
||||
if key == "numChapters":
|
||||
value = commaGroups("%d"%value)
|
||||
if key == "dateCreated":
|
||||
value = value.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if key == "datePublished" or key == "dateUpdated":
|
||||
@@ -279,7 +282,7 @@ class Story:
|
||||
# just for less clutter in adapters.
|
||||
def extendList(self,listname,l):
|
||||
for v in l:
|
||||
self.addToList(listname,v)
|
||||
self.addToList(listname,v.strip())
|
||||
|
||||
def addToList(self,listname,value):
|
||||
if value==None:
|
||||
|
||||
@@ -140,6 +140,101 @@ ${value}<br />
|
||||
</html>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Update Log</title>
|
||||
<link href="stylesheet.css" type="text/css" charset="UTF-8" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Update Log</h3>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_ENTRY = string.Template('''
|
||||
<b>${label}:</b> <span id="${id}">${value}</span>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_PAGE_END = string.Template('''
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
|
||||
def writeLogPage(self, out):
|
||||
"""
|
||||
XXX
|
||||
|
||||
|
||||
Write the log page, but only include entries that there's
|
||||
metadata for. START, ENTRY and END are expected to already by
|
||||
string.Template(). START and END are expected to use the same
|
||||
names as Story.metadata, but ENTRY should use id, label and value.
|
||||
"""
|
||||
if self.getConfig("include_logpage"):
|
||||
|
||||
# if there's a self.story.logfile, there's an existing log
|
||||
# to add to.
|
||||
if self.story.logfile:
|
||||
print("existing logfile found, appending")
|
||||
print("existing data:%s"%self._getLastLogData(self.story.logfile))
|
||||
replace_string = "</body>" # "</h3>"
|
||||
self._write(out,self.story.logfile.replace(replace_string,self._makeLogEntry(self._getLastLogData(self.story.logfile))+replace_string))
|
||||
else:
|
||||
# otherwise, write a new one.
|
||||
self._write(out,self.EPUB_LOG_PAGE_START.substitute(self.story.getAllMetadata()))
|
||||
self._write(out,self._makeLogEntry())
|
||||
self._write(out,self.EPUB_LOG_PAGE_END.substitute(self.story.getAllMetadata()))
|
||||
|
||||
# self parsing instead of Soup because it should be simple and not
|
||||
# worth the overhead.
|
||||
def _getLastLogData(self,logfile):
|
||||
"""
|
||||
Make a dict() of the most recent(last) log entry for each piece of metadata.
|
||||
Switch rindex to index to search from top instead of bottom.
|
||||
"""
|
||||
values = {}
|
||||
for entry in self.getConfigList("logpage_entries"):
|
||||
if entry in self.validEntries:
|
||||
try:
|
||||
# <span id="dateUpdated">1975-04-15</span>
|
||||
span = '<span id="%s">'%entry
|
||||
idx = logfile.rindex(span)+len(span)
|
||||
values[entry] = logfile[idx:logfile.index('</span>',idx)]
|
||||
except Exception, e:
|
||||
#print("e:%s"%e)
|
||||
pass
|
||||
|
||||
return values
|
||||
|
||||
def _makeLogEntry(self, oldvalues={}):
|
||||
retval = "<p class='log_entry'>"
|
||||
|
||||
for entry in self.getConfigList("logpage_entries"):
|
||||
if entry in self.validEntries:
|
||||
val = self.story.getMetadata(entry)
|
||||
if val and ( entry not in oldvalues or val != oldvalues[entry] ):
|
||||
if self.hasConfig(entry+"_label"):
|
||||
label=self.getConfig(entry+"_label")
|
||||
else:
|
||||
print("Using fallback label for %s_label"%entry)
|
||||
label=self.titleLabels[entry]
|
||||
|
||||
retval = retval + self.EPUB_LOG_ENTRY.substitute({'id':entry,
|
||||
'label':label,
|
||||
'value':val})
|
||||
else:
|
||||
# could be useful for introducing extra text, but
|
||||
# mostly it makes it easy to tell when you get the
|
||||
# keyword wrong.
|
||||
retval = retval + entry
|
||||
|
||||
retval = retval + "</p><hr />"
|
||||
|
||||
if self.getConfig('replace_hr'):
|
||||
retval = retval.replace("<hr />","<div class='center'>* * *</div>")
|
||||
|
||||
return retval
|
||||
|
||||
def writeStoryImpl(self, out):
|
||||
|
||||
## Python 2.5 ZipFile is rather more primative than later
|
||||
@@ -349,6 +444,11 @@ div { margin: 0pt; padding: 0pt; }
|
||||
if len(self.story.getChapters()) > 1 and self.getConfig("include_tocpage") and not self.metaonly :
|
||||
items.append(("toc_page","OEBPS/toc_page.xhtml","application/xhtml+xml","Table of Contents"))
|
||||
itemrefs.append("toc_page")
|
||||
|
||||
if self.getConfig("include_logpage"):
|
||||
items.append(("log_page","OEBPS/log_page.xhtml","application/xhtml+xml","Update Log"))
|
||||
itemrefs.append("log_page")
|
||||
|
||||
for index, (title,html) in enumerate(self.story.getChapters()):
|
||||
if html:
|
||||
i=index+1
|
||||
@@ -480,6 +580,13 @@ div { margin: 0pt; padding: 0pt; }
|
||||
outputepub.writestr("OEBPS/toc_page.xhtml",tocpageIO.getvalue())
|
||||
tocpageIO.close()
|
||||
|
||||
# write log page.
|
||||
logpageIO = StringIO.StringIO()
|
||||
self.writeLogPage(logpageIO)
|
||||
if logpageIO.getvalue(): # will be false if no log page.
|
||||
outputepub.writestr("OEBPS/log_page.xhtml",logpageIO.getvalue())
|
||||
logpageIO.close()
|
||||
|
||||
for index, (title,html) in enumerate(self.story.getChapters()):
|
||||
if html:
|
||||
logging.debug('Writing chapter text for: %s' % title)
|
||||
|
||||
+1
-8
@@ -54,13 +54,6 @@
|
||||
much easier. </p>
|
||||
</div>
|
||||
<!-- put announcements here, h3 is a good title size. -->
|
||||
<h3>New Sites</h3>
|
||||
<p>
|
||||
|
||||
New sites grangerenchanted.com, hlfiction.net and nha.magical-worlds.us.
|
||||
<br />Thanks, Ida!
|
||||
|
||||
</p>
|
||||
<p>
|
||||
Questions? Check out our
|
||||
<a href="http://code.google.com/p/fanficdownloader/wiki/FanFictionDownloaderFAQs">FAQs</a>.
|
||||
@@ -69,7 +62,7 @@
|
||||
If you have any problems with this application, please
|
||||
report them in
|
||||
the <a href="http://groups.google.com/group/fanfic-downloader">FanFictionDownLoader Google Group</a>. The
|
||||
<a href="http://4-4-20.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
<a href="http://4-4-21.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
</p>
|
||||
<div id='error'>
|
||||
{{ error_message }}
|
||||
|
||||
+17
-3
@@ -19,8 +19,7 @@
|
||||
## overridden at several levels
|
||||
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
## adult content.
|
||||
#is_adult:true
|
||||
|
||||
## All available titlepage_entries and the label used for them:
|
||||
@@ -183,9 +182,24 @@ windows_eol: true
|
||||
[epub]
|
||||
|
||||
## epub carries the TOC in metadata.
|
||||
## mobi generated from epub will have a TOC at the end.
|
||||
## mobi generated from epub by calibre will have a TOC at the end.
|
||||
include_tocpage: false
|
||||
|
||||
## include a Update Log page before the story text. If included, the
|
||||
## log will be updated each time the epub is an all the metadata
|
||||
## fields that have changed since the last update (typically
|
||||
## dateUpdated,numChapters,numWords at a minimum) will be shown.
|
||||
## Great for tracking when chapters came out and when the description,
|
||||
## etc changed.
|
||||
include_logpage: false
|
||||
|
||||
## items to include in the log page Empty metadata entries, or those
|
||||
## that haven't changed since the last update, will *not* appear, even
|
||||
## if in the list. You can include extra text or HTML that will be
|
||||
## included as-is in each log entry. Eg: logpage_entries: ...,<br />,
|
||||
## summary,<br />,...
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,title,author,description,category,genre,rating,warnings
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
|
||||
+1
-2
@@ -3,8 +3,7 @@
|
||||
|
||||
[defaults]
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
## adult content.
|
||||
#is_adult:true
|
||||
|
||||
## include images from img tags in the body and summary of
|
||||
|
||||
Reference in New Issue
Block a user