Compare commits

...
9 changed files with 231 additions and 25 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# ffd-retief-hrd fanfictiondownloader
application: fanfictiondownloader
version: 4-4-4
version: 4-4-5
runtime: python27
api_version: 1
threadsafe: true
+1 -1
View File
@@ -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, 5, 10)
version = (1, 5, 13)
minimum_calibre_version = (0, 8, 30)
#: This field defines the GUI plugin class that contains all the code
+12 -1
View File
@@ -109,6 +109,9 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
# Assign our menu to this action
self.menu = QMenu(self.gui)
self.old_actions_unique_map = {}
# menu_actions is just to keep a live reference to the menu
# items to prevent GC removing it.
self.menu_actions = []
self.qaction.setMenu(self.menu)
self.menu.aboutToShow.connect(self.about_to_show_menu)
@@ -135,6 +138,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
do_user_config = self.interface_action_base_plugin.do_user_config
self.menu.clear()
self.actions_unique_map = {}
self.menu_actions = []
self.add_action = self.create_menu_item_ex(self.menu, '&Add New from URL(s)', image='plus.png',
unique_name='Add New FanFiction Book(s) from URL(s)',
shortcut_name='Add New FanFiction Book(s) from URL(s)',
@@ -227,6 +231,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
ac = create_menu_action_unique(self, parent_menu, menu_text, image, tooltip,
shortcut, triggered, is_checked, shortcut_name, unique_name)
self.actions_unique_map[ac.calibre_shortcut_unique_name] = ac.calibre_shortcut_unique_name
self.menu_actions.append(ac)
return ac
def plugin_button(self):
@@ -768,7 +773,13 @@ make_firstimage_cover:true
if epubmi.cover_data[1] is not None:
db.set_cover(book_id, epubmi.cover_data[1])
#mi.cover = epubmi.cover_data[1]
# set author link if found. All current adapters have authorUrl.
if 'authorUrl' in book['all_metadata']:
autid=db.get_author_id(book['author'])
db.set_link_field_for_author(autid, unicode(book['all_metadata']['authorUrl']),
commit=False, notify=False)
db.set_metadata(book_id,mi)
# do configured column updates here.
+1
View File
@@ -51,6 +51,7 @@ import adapter_thequidditchpitchorg
import adapter_nfacommunitycom
import adapter_midnightwhispersca
import adapter_ksarchivecom
import adapter_archiveskyehawkecom
## This bit of complexity allows adapters to be added by just adding
## importing. It eliminates the long if/else clauses we used to need
@@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Fanficdownloader team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import time
import logging
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
def getClass():
return ArchiveSkyeHawkeComAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class ArchiveSkyeHawkeComAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.decode = ["Windows-1252",
"utf8"] # 1252 is a superset of iso-8859-1.
# Most sites that claim to be
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
self.password = ""
self.is_adult=False
# get storyId from url--url validation guarantees query is only sid=1234
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/story.php?no='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ash')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%Y-%m-%d"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'archive.skyehawke.com'
def getSiteExampleURLs(self):
return "http://"+self.getSiteDomain()+"/story.php?no=1234"
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/story.php?no=")+r"\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
url = self.url
logging.debug("URL: "+url)
try:
data = self._fetchUrl(url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
data = self._fetchUrl(url)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('div', {'class':"story border"}).find('span',{'class':'left'})
title=a.text.split('"')[1]
self.story.setMetadata('title',title)
# Find authorid and URL from... author url.
author = a.find('a')
self.story.setMetadata('authorId',author['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+author['href'])
self.story.setMetadata('author',author.string)
authorSoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
chapter=soup.find('select',{'name':'chapter'}).findAll('option')
for i in range(1,len(chapter)):
ch=chapter[i]
self.chapterUrls.append((stripHTML(ch),ch['value']))
self.story.setMetadata('numChapters',len(self.chapterUrls))
# eFiction sites don't help us out a lot with their meta data
# formating, so it's a little ugly.
box=soup.find('div', {'class': "container borderridge"})
sum=box.find('span').text
self.setDescription(url,sum)
boxes=soup.findAll('div', {'class': "container bordersolid"})
for box in boxes:
if box.find('b') != None and box.find('b').text == "History and Story Information":
for b in box.findAll('b'):
if "words" in b.nextSibling:
self.story.setMetadata('numWords', b.text)
if "archived" in b.previousSibling:
self.story.setMetadata('datePublished', makeDate(stripHTML(b.text), self.dateformat))
if "updated" in b.previousSibling:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(b.text), self.dateformat))
if "fandom" in b.nextSibling:
self.story.addToList('category', b.text)
for br in box.findAll('br'):
br.replaceWith('split')
genre=box.text.split("Genre:")[1].split("split")[0]
if not "Unspecified" in genre:
self.story.addToList('genre',genre)
if box.find('span') != None and box.find('span').text == "WARNING":
rating=box.findAll('span')[1]
rating.find('br').replaceWith('split')
rating=rating.text.replace("This story is rated",'').split('split')[0]
self.story.setMetadata('rating',rating)
logging.debug(self.story.getMetadata('rating'))
warnings=box.find('ol')
if warnings != None:
warnings=warnings.text.replace(']', '').replace('[', '').split(' ')
for warning in warnings:
self.story.addToList('warnings',warning)
for asoup in authorSoup.findAll('div', {'class':"story bordersolid"}):
if asoup.find('a')['href'] == 'story.php?no='+self.story.getMetadata('storyId'):
if '[ Completed ]' in asoup.text:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
chars=asoup.findNext('div').text.split('Characters')[1].split(']')[0]
for char in chars.split(','):
if not "None" in char:
self.story.addToList('characters',char)
break
# grab the text for an individual chapter.
def getChapterText(self, url):
logging.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
div = soup.find('div',{'class':"chapter bordersolid"}).findNext('div').findNext('div')
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+3 -4
View File
@@ -331,10 +331,6 @@ class BaseSiteAdapter(Configurable):
# This is primarily for epub updates.
return re.sub(r"</?body>\r?\n?","",retval)
fullmon = {"January":"01", "February":"02", "March":"03", "April":"04", "May":"05",
"June":"06","July":"07", "August":"08", "September":"09", "October":"10",
"November":"11", "December":"12" }
def cachedfetch(realfetch,cache,url):
if url in cache:
print("cache hit")
@@ -342,6 +338,9 @@ def cachedfetch(realfetch,cache,url):
else:
return realfetch(url)
fullmon = {"January":"01", "February":"02", "March":"03", "April":"04", "May":"05",
"June":"06","July":"07", "August":"08", "September":"09", "October":"10",
"November":"11", "December":"12" }
def makeDate(string,format):
# Surprise! Abstracting this turned out to be more useful than
+10 -6
View File
@@ -23,11 +23,16 @@ def _unirepl(match):
radix=16
else:
radix=10
value = int(match.group(2), radix )
return unichr(value)
value = int(match.group(2), radix)
return "%s%s"%(unichr(value),match.group(3))
def _replaceNumberEntities(data):
p = re.compile(r'&#(x?)([0-9a-fA-F]+);')
# The same brokenish entity parsing in SGMLParser that inserts ';'
# after non-entities will also insert ';' incorrectly after number
# entities, including part of the next word if it's a-z.
# "Don't&#8212ever&#8212do&#8212that&#8212again," becomes
# "Don't&#8212e;ver&#8212d;o&#8212;that&#8212a;gain,"
p = re.compile(r'&#(x?)([0-9a-fA-F]{,4})([0-9a-fA-F]*);')
return p.sub(_unirepl, data)
def _replaceNotEntities(data):
@@ -50,9 +55,6 @@ def removeAllEntities(text):
return removeEntities(text).replace('&lt;', '<').replace('&gt;', '>').replace('&amp;', '&')
def removeEntities(text):
# replace numeric versions of [&<>] with named versions,
# then replace named versions with actual characters,
if text is None:
return ""
@@ -67,6 +69,8 @@ def removeEntities(text):
except UnicodeEncodeError, e:
t = text
text = t
# replace numeric versions of [&<>] with named versions,
# then replace named versions with actual characters,
text = re.sub(r'&#0*38;','&amp;',text)
text = re.sub(r'&#0*60;','&lt;',text)
text = re.sub(r'&#0*62;','&gt;',text)
+1 -1
View File
@@ -216,7 +216,7 @@ class Story:
for (p,v) in self.replacements:
if (isinstance(value,str) or isinstance(value,unicode)) and re.match(p,value):
value = re.sub(p,v,value)
return value;
return value
def getMetadata(self, key, removeallentities=False):
value = None
+12 -11
View File
@@ -56,17 +56,17 @@
<!-- put announcements here, h3 is a good title size. -->
<h3>New Site Added</h3>
<p>
Support for ksarchive.com has been added. Thanks for Jade AislinSam implementing this.
Support for archive.skyehawke.com has been added. Thanks to Ida Leter for implementing this.
</p>
<p>
Questions? Check out our new
<a href="http://code.google.com/p/fanficdownloader/wiki/FanFictionDownloaderFAQs">FAQs</a>. Thanks to Wyndham for writing these.
Questions? Check out our
<a href="http://code.google.com/p/fanficdownloader/wiki/FanFictionDownloaderFAQs">FAQs</a>.
</p>
<p>
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-3.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
<a href="http://4-4-4.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
</p>
<div id='error'>
{{ error_message }}
@@ -248,10 +248,6 @@
<br /><a href="http://ficbook.net/readfic/93626">http://ficbook.net/readfic/93626</a>.
<br /><a href="http://ficbook.net/readfic/93626/246417#part_content">http://ficbook.net/readfic/93626/246417#part_content</a>.
</dd>
<dt>gayauthors.org</dt>
<dd>
Removed following complaints by the site administration.
</dd>
<dt>fanfiction.mugglenet.com</dt>
<dd>
Use the URL of the story's chapter list, such as
@@ -287,18 +283,23 @@
Use the URL of the story's chapter list, such as
<br /><a href="http://ksarchive.com/viewstory.php?sid=1124">http://ksarchive.com/viewstory.php?sid=1124</a>.
</dd>
<dt>archive.skyehawke.com</dt>
<dd>
Use the URL of the story's summary, such as
<br /><a href="http://archive.skyehawke.com/story.php?no=17466">http://archive.skyehawke.com/story.php?no=17466</a>.
</dd>
</dl>
<p>
A few additional things to know, which will make your life substantially easier:
</p>
<ol>
<li>
First thing to know: I do not use your Google login and password. In fact, all I know about it is your ID &ndash; password
First thing to know: We do not use your Google login and password. In fact, all we know about it is your ID &ndash; password
is being verified by Google and is absolutely, totally unknown to anyone but you.
</li>
<li>
Small <a href="http://www.sigizmund.com/reading-fanfiction-off-line-in-stanza-and-oth">post written by me</a>
Small <a href="http://www.sigizmund.com/reading-fanfiction-off-line-in-stanza-and-oth">post written by Roman</a>
&mdash; how to read fiction in Stanza or any other ebook reader.
</li>
<li>
@@ -314,7 +315,7 @@
</li>
<li>
If you think that something that should work in fact doesn't, post a message to
our <a href="http://groups.google.com/group/fanfic-downloader">Google Group</a>. I also encourage you to join it so
our <a href="http://groups.google.com/group/fanfic-downloader">Google Group</a>. we also encourage you to join it so
you will find out about latest updates and fixes as soon as possible
</li>
</ol>