Compare commits

...
13 Commits
19 changed files with 2278 additions and 356 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import sys
@@ -42,7 +42,7 @@ class FanFicFareBase(InterfaceActionBase):
description = _('UI plugin to download FanFiction stories from various sites.')
supported_platforms = ['windows', 'osx', 'linux']
author = 'Jim Miller'
version = (2, 2, 16)
version = (2, 2, 17)
minimum_calibre_version = (1, 48, 0)
#: This field defines the GUI plugin class that contains all the code
+3 -2
View File
@@ -608,8 +608,9 @@ class FanFicFarePlugin(InterfaceAction):
status_prefix=_("Books UnNewed"))
def get_unnew_books_loop(self,book,db=None,tdir=None):
if book['calibre_id'] and db.has_format(book['calibre_id'],'EPUB',index_is_id=True):
book['changed']=False
if book['calibre_id'] and db.has_format(book['calibre_id'],'EPUB',index_is_id=True) and self.get_story_url(db,book['calibre_id']):
tmp = PersistentTemporaryFile(prefix='%s-'%book['calibre_id'],
suffix='.epub',
dir=tdir)
+20 -12
View File
@@ -380,11 +380,9 @@ sort_ships:false
## ships entry and then, after => the string to use to merge the parts
## back together. \s == blank space.
## Each part will have replace_metadata with key ships_CHARS applied.
## By default, sort_ships_splits:/=>/
## Here's possible an example for AO3:
#sort_ships_splits:
# [ ]*/[ ]*=>/
# [ ]*&[ ]*=>\s&\s
sort_ships_splits:
[ ]*/[ ]*=>/
[ ]*&[ ]*=>\s&\s
## join_string_<entry> options -- FanFicFare list entries are comma
## separated by default. You can use this to change that. For example,
@@ -1482,12 +1480,6 @@ extraships:Sam/Dean
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[sg1-heliopolis.com]
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
## this should go in your personal.ini, not defaults.ini.
#is_adult:true
[sheppardweir.com]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -2004,9 +1996,18 @@ extracategories:Harry Potter
extraships:Harry Potter/Ginny Weasley
[www.potionsandsnitches.org]
extra_valid_entries:stars,reviews,reads,takesplaces,snapeflavours,sitetags
stars_label:Frogs
takesplaces_label:Takes Place
snapeflavours_label:Snape Flavour
sitetags_labels:Site Tags
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
add_to_include_subject_tags:,takesplaces,snapeflavours,sitetags
#add_to_extra_titlepage_entries:,stars,reviews,reads,takesplaces,snapeflavours,sitetags
[www.potterfics.com]
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
@@ -2030,11 +2031,15 @@ extracategories:Psych
extracategories:Queer as Folk
[quotev.com]
extra_valid_entries:pages,readers,reads,favorites
extra_valid_entries:pages,readers,reads,favorites,searchtags,comments
pages_label:Pages
readers_label:Readers
reads_label:Reads
favorites_label:Favorites
searchtags:Search Tags
comments:Comments
include_in_category:category,searchtags
[www.restrictedsection.org]
## Some sites require login (or login for some rated stories) The
@@ -2172,6 +2177,9 @@ extracategories:Twilight
collect_series: false
[www.walkingtheplank.org]
extra_valid_entries:reads
reads_label:Read Count
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
## this should go in your personal.ini, not defaults.ini.
+6 -6
View File
@@ -5,15 +5,15 @@
# Ettore Atalan <atalanttore@googlemail.com>, 2014-2015
# ILB, 2014-2015
# Sebastian Keller <Haggard@gmx.de>, 2015
# Simon_Schuette <simonschuette@arcor.de>, 2014-2015
# Simon_Schuette <simonschuette@arcor.de>, 2014-2016
# Simon S, 2015
# Valsu <valsu@hylia.de>, 2015
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2015-12-13 10:41+Central Standard Time\n"
"PO-Revision-Date: 2015-12-14 09:49+0000\n"
"Last-Translator: Kovid Goyal <kovid@kovidgoyal.net>\n"
"PO-Revision-Date: 2016-01-04 19:34+0000\n"
"Last-Translator: Simon_Schuette <simonschuette@arcor.de>\n"
"Language-Team: German (http://www.transifex.com/calibre/calibre-plugins/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -981,7 +981,7 @@ msgstr ""
#: config.py:1343
msgid "Set Calibre Author URL"
msgstr ""
msgstr "Calibre Autor URL festlegen"
#: config.py:1344
msgid "Set Calibre Author URL to Author's URL on story site."
@@ -1944,7 +1944,7 @@ msgstr "Fehler beim Herunterladen der Metadaten"
msgid ""
"An error has occurred while FanFicFare was updating calibre's metadata for "
"<a href='%s'>%s</a>."
msgstr ""
msgstr "Während FanFicFare die Calibre-Metadaten für <a href='%s'>%s</a> aktualisierte, trat ein Fehler auf."
#: fff_plugin.py:1517
msgid "The ebook has been updated, but the metadata has not."
@@ -2094,7 +2094,7 @@ msgstr "FanFiction-Geschichten herunterladen"
#: jobs.py:94
msgid "Download Results:"
msgstr ""
msgstr "Download Ergebnisse:"
#: jobs.py:96
msgid "Successful:"
File diff suppressed because it is too large Load Diff
-1
View File
@@ -75,7 +75,6 @@ import adapter_ncisfictionnet
import adapter_thealphagatecom
import adapter_fanfiktionde
import adapter_ponyfictionarchivenet
import adapter_sg1heliopoliscom
import adapter_ncisficcom
import adapter_nationallibrarynet
import adapter_themasquenet
@@ -126,6 +126,9 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
if 'Word count' in label:
self.story.setMetadata('numWords', value)
if 'Read' in label:
self.story.setMetadata('reads', value)
if 'Categories' in label:
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
catstext = [cat.string for cat in cats]
@@ -142,12 +145,30 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
else:
self.story.addToList('characters',char.string)
if 'Warning' in label:
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for warning in warnings:
self.story.addToList('warnings',stripHTML(warning))
if 'Genre' in label:
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
genrestext = [genre.string for genre in genres]
self.genre = ', '.join(genrestext)
for genre in genrestext:
self.story.addToList('genre',genre.string)
for genre in genres:
self.story.addToList('genre',stripHTML(genre))
if 'Takes Place' in label:
takesplaces = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for takesplace in takesplaces:
self.story.addToList('takesplaces',stripHTML(takesplace))
if 'Snape flavour' in label:
snapeflavours = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for snapeflavour in snapeflavours:
self.story.addToList('snapeflavours',stripHTML(snapeflavour))
if 'Tags' in label:
sitetags = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for sitetag in sitetags:
self.story.addToList('sitetags',stripHTML(sitetag))
if 'Completed' in label:
if 'Yes' in value:
@@ -183,6 +204,14 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
# I find it hard to care if the series parsing fails
pass
divsort = soup.find('div',id='sort')
stars = len(divsort.find_all('img',src='images/star.gif'))
stars = stars + 0.5 * len(divsort.find_all('img',src='images/starhalf.gif'))
self.story.setMetadata('stars',stars)
a = divsort.find_all('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
def getChapterText(self, url):
+50 -47
View File
@@ -5,6 +5,7 @@ import datetime
from .. import exceptions
from base_adapter import BaseSiteAdapter
from ..htmlcleanup import stripHTML
SITE_DOMAIN = 'quotev.com'
STORY_URL_TEMPLATE = 'http://www.quotev.com/story/%s'
@@ -53,76 +54,78 @@ class QuotevComAdapter(BaseSiteAdapter):
soup = self.make_soup(data)
element = soup.find('div', {'class': 'result_head'})
element = soup.find('div', {'class': 'result'})
if not element:
raise exceptions.StoryDoesNotExist(self.url)
self.story.setMetadata('title', element.find('span', recursive=False).get_text())
self.story.setMetadata('title', element.find('h1').get_text())
element = soup.find('div', {'class': 'desc_creator'})
if element:
a = element('a')[1]
self.story.setMetadata('author', a.get_text())
self.story.setMetadata('authorId', get_url_path_segments(a['href'])[0])
self.story.setMetadata('authorUrl', urlparse.urljoin(self.url, a['href']))
# Multiple authors
else:
element = soup.find('div', id='qheadx')
for a in element('div', recursive=False)[1]('a'):
author = a.get_text()
if not a.get_text():
continue
self.story.addToList('author', author)
self.story.addToList('authorId', get_url_path_segments(a['href'])[0])
# quotev html is all about formatting without any content tagging
authdiv = soup.find('div', {'style':"text-align:left;"})
if authdiv:
#print("div:%s"%authdiv.find_all('a'))
for a in authdiv.find_all('a'):
self.story.addToList('author', a.get_text())
self.story.addToList('authorId', a['href'].split('/')[-1])
self.story.addToList('authorUrl', urlparse.urljoin(self.url, a['href']))
else:
self.story.setMetadata('author','Anonymous')
self.story.setMetadata('authorUrl','http://www.quotev.com')
self.story.setMetadata('authorId','0')
self.setDescription(self.url, soup.find('div', id='qdesct'))
self.setCoverImage(self.url, urlparse.urljoin(self.url, soup.find('img', {'class': 'logo'})['src']))
for a in soup.find('div', {'class': 'tag'})('a'):
if a['href'] == '#':
continue
for a in soup.find_all('a', {'href': re.compile(SITE_DOMAIN+'/stories/c/')}):
self.story.addToList('category', a.get_text())
elements = soup('span', {'class': 'q_time'})
for a in soup.find_all('a', {'href': re.compile(SITE_DOMAIN+'/search/')}):
self.story.addToList('searchtags', a.get_text())
elements = soup.find_all('span', {'class': 'q_time'})
self.story.setMetadata('datePublished', datetime.datetime.fromtimestamp(float(elements[0]['ts'])))
if len(elements) > 1:
self.story.setMetadata('dateUpdated', datetime.datetime.fromtimestamp(float(elements[1]['ts'])))
metadiv = elements[0].parent
if 'completed' in stripHTML(metadiv):
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
data = filter(None, (x.strip() for x in stripHTML(metadiv).split(u'\xb7')))
for datum in data:
parts = datum.split()
if len(parts) < 2 or parts[1] not in self.getConfig('extra_valid_entries'):
continue
# Not a valid metadatum
# if not len(parts) == 2:
# continue
key, value = parts[1], parts[0]
self.story.setMetadata(key, value.replace(',', '').replace('.', ''))
favspans = soup.find('a',{'id':'fav_btn'}).find_all('span')
if len(favspans) > 1:
self.story.setMetadata('favorites', stripHTML(favspans[1]).replace(',', ''))
commentspans = soup.find('a',{'id':'comment_btn'}).find_all('span')
#print("commentspans:%s"%commentspans)
if len(commentspans) > 0:
self.story.setMetadata('comments', stripHTML(commentspans[0]).replace(',', ''))
for a in soup.find('div', id='rselect')('a'):
self.chapterUrls.append((a.get_text(), urlparse.urljoin(self.url, a['href'])))
self.story.setMetadata('numChapters', len(self.chapterUrls))
element = soup.find('div', {'class': 't'})('div', recursive=False)[1].div
data = filter(None, (x.strip() for x in element.get_text().split(u'\xb7')))
if 'completed' in data:
self.story.setMetadata('status', 'Completed')
data.remove('completed')
else:
self.story.setMetadata('status', 'In-Progress')
for datum in data:
parts = datum.split()
# Not a valid metadatum
if not len(parts) == 2:
continue
key, value = parts
self.story.setMetadata(key, value.replace(',', '').replace('.', ''))
self.story.setMetadata('favorites', soup.find('div', id='favqn').get_text())
element = soup.find('a', id='comment_btn').span
self.story.setMetadata('comments', element.get_text() if element else 0)
def getChapterText(self, url):
data = self._fetchUrl(url)
soup = self.make_soup(data)
element = soup.find('div', id='restxt')
element = soup.find('div', id='rescontent')
for a in element('a'):
a.unwrap()
@@ -1,258 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2012 Fanficdownloader team, 2015 FanFicFare 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.
#
# Software: eFiction
import time
import logging
logger = logging.getLogger(__name__)
import re
import urllib2
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
def getClass():
return SG1HeliopolisComAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class SG1HeliopolisComAdapter(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])
self.section=self.parsedUrl.path.split('/',)[1]
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/'+self.section+'/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','sghp')
# If all stories from the site fall into the same category,
# the site itself isn't likely to label them as such, so we
# do. Can't use extracategories, could be Atlantis or SG-1
if 'atlantis' in self.section:
self.story.addToList("category","Stargate: Atlantis")
else:
self.story.addToList("category","Stargate: SG-1")
# 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 'sg1-heliopolis.com'
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/archive/viewstory.php?sid=1234 http://"+cls.getSiteDomain()+"/adult/viewstory.php?sid=1234 http://"+cls.getSiteDomain()+"/atlantis/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return r"http://sg1-heliopolis.com/(archive|adult|atlantis)?/viewstory.php\?sid=\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
if self.is_adult or self.getConfig("is_adult"):
# Weirdly, different sites use different warning numbers.
# If the title search below fails, there's a good chance
# you need a different number. print data at that point
# and see what the 'click here to continue' url says.
addurl = "&warning=4"
else:
addurl=""
# index=1 makes sure we see the story chapter index. Some
# sites skip that for one-chapter stories.
url = self.url+'&index=1'+addurl
logger.debug("URL: "+url)
try:
data = self._fetchUrl(url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
m = re.search(r"'viewstory.php\?sid=\d+((?:&amp;ageconsent=ok)?&amp;warning=\d+)'",data)
if m != None:
if self.is_adult or self.getConfig("is_adult"):
# We tried the default and still got a warning, so
# let's pull the warning number from the 'continue'
# link and reload data.
addurl = m.group(1)
# correct stupid &amp; error in url.
addurl = addurl.replace("&amp;","&")
url = self.url+'&index=1'+addurl
logger.debug("URL 2nd try: "+url)
try:
data = self._fetchUrl(url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
else:
raise exceptions.AdultCheckRequired(self.url)
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.
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
self.story.setMetadata('author',a.string)
# Find the chapters:
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
# just in case there's tags, like <i> in chapter titles.
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+self.section+'/'+chapter['href']+addurl))
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.
# utility method
def defaultGetattr(d,k):
try:
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
labels = soup.findAll('span',{'class':'label'})
for labelspan in labels:
value = labelspan.nextSibling
label = labelspan.string
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
if 'Rated' in label:
self.story.setMetadata('rating', value)
if 'Word count' in label:
self.story.setMetadata('numWords', value)
if 'Categories' in label:
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for cat in cats:
self.story.addToList('category',cat.string)
if 'Characters' in label:
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
self.story.addToList('characters',char.string)
if 'Genre' in label:
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
for genre in genres:
self.story.addToList('genre',genre.string)
if 'Warnings' in label:
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
for warning in warnings:
self.story.addToList('warnings',warning.string)
if 'Completed' in label:
if 'Yes' in value:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
try:
# Find Series name from series URL.
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
series_name = a.string
series_url = 'http://'+self.host+'/'+self.section+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = self.make_soup(self._fetchUrl(series_url))
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
i=1
for a in storyas:
# skip 'report this' and 'TOC' links
if 'contact.php' not in a['href'] and 'index' not in a['href']:
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
self.setSeries(series_name, i)
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'story'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+3 -3
View File
@@ -3,7 +3,7 @@ import re
import urllib2
import urlparse
from bs4 import BeautifulSoup
from bs4.element import Tag
from ..htmlcleanup import stripHTML
from base_adapter import BaseSiteAdapter, makeDate
@@ -98,7 +98,7 @@ class SpikeluverComAdapter(BaseSiteAdapter):
query_data = urlparse.parse_qs(components.query)
self.story.setMetadata('author', stripHTML(author_anchor))
self.story.setMetadata('authorId', query_data['uid'])
self.story.setMetadata('authorId', query_data['uid'][0])
self.story.setMetadata('authorUrl', url)
sort_div = soup.find('div', id='sort')
@@ -122,7 +122,7 @@ class SpikeluverComAdapter(BaseSiteAdapter):
keep_summary_html = self.getConfig('keep_summary_html')
for sibling in _yield_next_siblings(span_tag):
if isinstance(sibling, BeautifulSoup.Tag):
if isinstance(sibling, Tag):
# Encountered next label, break. Not as bad as other
# e-fiction sites, let's hope this is enough for proper
# parsing.
+2 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2011 Fanficdownloader team, 2015 FanFicFare team
# Copyright 2011 Fanficdownloader team, 2016 FanFicFare team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -172,7 +172,7 @@ class TwistingTheHellmouthSiteAdapter(BaseSiteAdapter):
descurl=authorurl
authorsoup = self.make_soup(authordata)
# author can have several pages, scan until we find it.
while( not authorsoup.find('a', href=re.compile(r"^/Story-"+self.story.getMetadata('storyId'))) ):
while( not authorsoup.find('a', href=re.compile(r"^/Story-"+self.story.getMetadata('storyId')+'/')) ):
nextarrow = authorsoup.find('a', {'class':'arrowf'})
if not nextarrow:
## if rating is set lower than story, it won't be
@@ -138,7 +138,7 @@ class Voracity2EficComAdapter(BaseSiteAdapter):
query_data = urlparse.parse_qs(components.query)
self.story.setMetadata('author', author_anchor.string)
self.story.setMetadata('authorId', query_data['uid'])
self.story.setMetadata('authorId', query_data['uid'][0])
self.story.setMetadata('authorUrl', url)
sort_div = soup.find('div', id='sort')
@@ -161,6 +161,9 @@ class WalkingThePlankOrgAdapter(BaseSiteAdapter):
if 'Word count' in label:
self.story.setMetadata('numWords', value)
if 'Read' in label:
self.story.setMetadata('reads', value)
if 'Categories' in label:
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
catstext = [cat.string for cat in cats]
@@ -168,7 +168,14 @@ class BaseXenForoForumAdapter(BaseSiteAdapter):
threadmarksa = soup.find('a',{'class':'threadmarksTrigger'})
if threadmarksa:
soupmarks = self.make_soup(self._fetchUrl(self.getURLPrefix()+'/'+threadmarksa['href']))
markas = soupmarks.find('ol',{'class':'overlayScroll'}).find_all('a')
markas = []
ol = soupmarks.find('ol',{'class':'overlayScroll'})
if ol:
markas = ol.find_all('a')
else:
## SV changed their threadmarks. Not isolated to
## SV only incase SB or QQ make the same change.
markas = soupmarks.find('div',{'class':'threadmarks'}).find_all('a',{'class':'PreviewTooltip'})
if len(markas) > 1:
for (atag,url,name) in [ (x,x['href'],stripHTML(x)) for x in markas ]:
date = self.make_date(atag.find_next_sibling('div',{'class':'extra'}))
+20 -12
View File
@@ -379,11 +379,9 @@ sort_ships:false
## ships entry and then, after => the string to use to merge the parts
## back together. \s == blank space.
## Each part will have replace_metadata with key ships_CHARS applied.
## By default, sort_ships_splits:/=>/
## Here's possible an example for AO3:
#sort_ships_splits:
# [ ]*/[ ]*=>/
# [ ]*&amp;[ ]*=>\s&amp;\s
sort_ships_splits:
[ ]*/[ ]*=>/
[ ]*&amp;[ ]*=>\s&amp;\s
## join_string_<entry> options -- FanFicFare list entries are comma
## separated by default. You can use this to change that. For example,
@@ -1470,12 +1468,6 @@ extraships:Sam/Dean
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[sg1-heliopolis.com]
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
## this should go in your personal.ini, not defaults.ini.
#is_adult:true
[sheppardweir.com]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -1986,9 +1978,18 @@ extracategories:Harry Potter
extraships:Harry Potter/Ginny Weasley
[www.potionsandsnitches.org]
extra_valid_entries:stars,reviews,reads,takesplaces,snapeflavours,sitetags
stars_label:Frogs
takesplaces_label:Takes Place
snapeflavours_label:Snape Flavour
sitetags_labels:Site Tags
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
add_to_include_subject_tags:,takesplaces,snapeflavours,sitetags
#add_to_extra_titlepage_entries:,stars,reviews,reads,takesplaces,snapeflavours,sitetags
[www.potterfics.com]
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
@@ -2012,11 +2013,15 @@ extracategories:Psych
extracategories:Queer as Folk
[quotev.com]
extra_valid_entries:pages,readers,reads,favorites
extra_valid_entries:pages,readers,reads,favorites,searchtags,comments
pages_label:Pages
readers_label:Readers
reads_label:Reads
favorites_label:Favorites
searchtags:Search Tags
comments:Comments
include_in_category:category,searchtags
[www.restrictedsection.org]
## Some sites require login (or login for some rated stories) The
@@ -2154,6 +2159,9 @@ extracategories:Twilight
collect_series: false
[www.walkingtheplank.org]
extra_valid_entries:reads
reads_label:Read Count
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
## this should go in your personal.ini, not defaults.ini.
+11 -2
View File
@@ -816,10 +816,15 @@ class Story(Configurable):
for x in retlist:
curlist = []
for y in re.split(splitre,x):
## logger.debug("x:(%s) y:(%s)"%(x,y))
## for SPLIT_META(\,)
if doreplacements:
if x != y and doreplacements: # doreplacements always true here (currently)
y = self.doReplacements(y,'ships_CHARS',return_list=True)
else:
## needs to be a list to extend curlist.
y=[x]
curlist.extend(y)
## logger.debug("curlist:%s"%(curlist,))
newretlist.append( splitmerge.join(sorted(curlist)) )
retlist = newretlist
@@ -1109,4 +1114,8 @@ def commaGroups(s):
def unique_list(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
try:
return [x for x in seq if not (x in seen or seen_add(x))]
except:
print("unique_list exception seq:%s"%seq)
raise
+1 -1
View File
@@ -23,7 +23,7 @@ setup(
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version="2.2.166",
version="2.2.17",
description='A tool for downloading fanfiction to eBook formats',
long_description=long_description,
+1 -1
View File
@@ -1,6 +1,6 @@
# ffd-retief-hrd fanficfare
application: fanficfare
version: 2-2-16
version: 2-2-17
runtime: python27
api_version: 1
threadsafe: true
+1 -1
View File
@@ -35,7 +35,7 @@
If you have any problems with this application, please
report them in
the <a href="http://groups.google.com/group/fanfic-downloader">FanFicFare Google Group</a>. The
<a href="http://2-2-15.fanficfare.appspot.com">previous version
<a href="http://2-2-16.fanficfare.appspot.com">previous version
</a> is also available for you to use if necessary.
</p>
<div id='error'>