Compare commits

...
50 Commits
Author SHA1 Message Date
Jim Miller f6d65c334f Bump versions. 2016-07-22 12:02:48 -05:00
Jim Miller 2c5371d95e Update translations. 2016-07-22 11:59:42 -05:00
Jim Miller 6f4660763f Save addheaders when setting cookiejar. For ffn referer. 2016-07-17 17:08:33 -05:00
Jim Miller 75f8f72266 Include 'prefix' tags in forumtags in base_xenforoforum. 2016-07-17 16:38:53 -05:00
Jim Miller b1d689ba3e Add n_anthaver and r_anthaver modes to custom_columns_settings for averaging metadata for anthologies before setting in integer and float calibre custom columns. 2016-07-12 14:57:10 -05:00
Jim Miller 39bb6e37f6 Add n_anthaver and r_anthaver modes to custom_columns_settings for averaging metadata for anthologies before setting in integer and float calibre custom columns. 2016-07-12 14:55:51 -05:00
Jim Miller 288f12afed Add n_anthaver and r_anthaver modes to custom_columns_settings for averaging metadata for anthologies before setting in integer and float calibre custom columns. 2016-07-12 14:53:18 -05:00
Jim Miller 2a25aef7ac Update translations. 2016-07-12 14:49:03 -05:00
Jim Miller 085fb47b08 Remove Django from app.yaml--old version going away. 2016-07-09 09:27:46 -05:00
Jim Miller 5fdcbab46a Allow old goto/post chapter URLs in base_xenforoforum. 2016-07-05 23:22:25 -05:00
Jim Miller 2d83fa8f5d Fix for SIYE when author puts story URL in bio. 2016-06-30 10:38:13 -05:00
Jim Miller 4a752e05e1 Change ffnet metadata colletion to allow for chars with (' - ') in them. 2016-06-26 11:34:36 -05:00
Jim Miller f1d9760aa9 Bump versions. 2016-06-23 17:08:47 -05:00
Jim Miller b11bdd82db Update translations 2016-06-23 17:07:12 -05:00
Jim Miller 6e5de8060e Update translations. 2016-06-22 23:52:19 -05:00
Jim Miller 2c1b9456bf Fix for previously failedtoload img tags causing lookup sleeps. 2016-06-21 11:23:57 -05:00
Jim Miller 60439cc658 Fix for older harrypotterfanfictioncom stories lacking reviewjs.js. 2016-06-21 11:23:20 -05:00
Jim Miller fb95e1e168 Change adapter_fictionmaniatv to set status Completed instead of Complete (no d). 2016-06-16 17:25:46 -05:00
Jim Miller 0d490d2e50 Update translations. 2016-06-15 23:29:41 -05:00
Jim Miller 838510c011 Update translations. 2016-06-15 23:28:50 -05:00
Jim Miller 954ad00ca6 Update StoriesOnlineNet/FineStoriesCom login URL. 2016-06-15 23:25:58 -05:00
Jim Miller ee462d3742 Different detect StoryDoesNotExist string for ficwad.com. 2016-06-10 09:54:45 -05:00
Jim Miller 56401e6dfa Allow old showpost.php chapter URLs in base_xenforoforum. 2016-06-10 09:52:02 -05:00
Jim Miller 6070accbf5 Update (& Fix) Translations. 2016-05-31 19:36:07 -05:00
Jim Miller eebb1ee8d0 Bump versions. 2016-05-23 13:47:11 -05:00
Jim Miller bda307ed3d Update translations. 2016-05-23 13:44:21 -05:00
Jim Miller 6b0c519f99 Make 'Go back to fix errors?' dialog scroll error list for smaller dialog size. 2016-05-22 15:55:00 -05:00
Jim Miller 6533ee0187 Remove defaults.ini sections for removed sites. 2016-05-21 16:45:16 -05:00
Jim Miller 3cc3b32012 Merge pull request #121 from cryzed/master
Fix for #120 
(ProceedQuestion got an unexpected keyword argument 'log_viewer_unique_name')
2016-05-21 10:11:58 -05:00
cryzed 315536a75b Fix for #120 2016-05-21 16:45:05 +02:00
Jim Miller 1c4250c0c1 Add merged tags to anthology epubs. 2016-05-19 09:27:19 -05:00
Jim Miller 6f7e700cd2 Fix for adapter_ashwindersycophanthexcom login. 2016-05-19 09:13:18 -05:00
Jim Miller a593bd201c Bump versions. 2016-05-17 12:34:08 -05:00
Jim Miller 37885a9039 Update translations. 2016-05-13 13:50:01 -05:00
Jim Miller 97437aa7da Fixes for ficbook.net changes. 2016-05-13 13:39:24 -05:00
Jim Miller ccfcd801a3 Fixes for ficbook.net changes. 2016-05-13 13:31:39 -05:00
Jim Miller 4df321c18e Remove support for dead sites. 2016-05-13 12:48:30 -05:00
Jim Miller 664fb639bd Unique name for ViewLog size. 2016-05-09 12:28:18 -05:00
Jim Miller 0cd71263b1 Change adapter_ncisfictionnet to eFiction Base due to site change. 2016-05-01 21:54:55 -05:00
Jim Miller 2fc1a14ac4 Change log status of NotGoingToDownload books to 'Skipped'. 2016-04-28 16:07:12 -05:00
Jim Miller 8772b50451 Update included html2text for web/plugin, require as dependency for CLI. 2016-04-28 14:22:33 -05:00
Jim Miller 7390424f0f Fix for Mac OS X freezes - set log level to CRITICAL unless calibre in debug or in BG job. 2016-04-24 23:38:24 -05:00
Jim Miller 3a16c49c2e Fix for Mac OS X crashes - changes to progress bar dialogs implementation. 2016-04-24 15:56:08 -05:00
Jim Miller d8066d4bcf Update translations. 2016-04-24 14:25:45 -05:00
Jim Miller 61fff980b5 Fix for Yet Another Site Change on quotevcom. 2016-04-22 21:41:53 -05:00
Jim Miller bf704fcdc7 Don't need seconds for published/updated on xenforo. 2016-04-22 21:30:53 -05:00
Jim Miller c9f86bd784 Fix for %p (am/pm) date parsing on non-en_US locales. 2016-04-22 21:24:00 -05:00
Jim Miller 76faea3b4e Comment out a bunch of adapter_storiesonlinenet debug output to avoid encoding issues. 2016-04-20 16:35:45 -05:00
Jim Miller 6b3a19bb45 Remove some more prints in the hopes it will help Mac. 2016-04-20 16:33:55 -05:00
Jim Miller df7bf64845 Update some webservice links/references. 2016-04-19 10:35:37 -05:00
55 changed files with 4582 additions and 3576 deletions
+10 -4
View File
@@ -7,15 +7,21 @@ __license__ = 'GPL v3'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import sys
import sys, os
if sys.version_info >= (2, 7):
import logging
logger = logging.getLogger(__name__)
loghandler=logging.StreamHandler()
loghandler.setFormatter(logging.Formatter("FFF: %(levelname)s: %(asctime)s: %(filename)s(%(lineno)d): %(message)s"))
logger.addHandler(loghandler)
loghandler.setLevel(logging.DEBUG)
logger.setLevel(logging.DEBUG)
from calibre.constants import DEBUG
if os.environ.get('CALIBRE_WORKER', None) is not None or DEBUG:
loghandler.setLevel(logging.DEBUG)
logger.setLevel(logging.DEBUG)
else:
loghandler.setLevel(logging.CRITICAL)
logger.setLevel(logging.CRITICAL)
# pulls in translation files for _() strings
try:
@@ -42,7 +48,7 @@ class FanFicFareBase(InterfaceActionBase):
description = _('UI plugin to download FanFiction stories from various sites.')
supported_platforms = ['windows', 'osx', 'linux']
author = 'Jim Miller'
version = (2, 3, 1)
version = (2, 3, 5)
minimum_calibre_version = (1, 48, 0)
#: This field defines the GUI plugin class that contains all the code
+45 -19
View File
@@ -21,19 +21,19 @@ from datetime import datetime
try:
from PyQt5 import QtWidgets as QtGui
from PyQt5 import QtCore
from PyQt5.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit,
QComboBox, QProgressDialog, QTimer, QDialogButtonBox,
QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal,
QGroupBox, QFrame, QTextBrowser, QSize, QAction)
from PyQt5.Qt import (QDialog, QWidget, QTableWidget, QVBoxLayout, QHBoxLayout,
QGridLayout, QPushButton, QFont, QLabel, QCheckBox, QIcon,
QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox,
QScrollArea, QPixmap, Qt, QAbstractItemView, QTextEdit,
pyqtSignal, QGroupBox, QFrame, QTextBrowser, QSize, QAction)
except ImportError as e:
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit,
QComboBox, QProgressDialog, QTimer, QDialogButtonBox,
QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal,
QGroupBox, QFrame, QTextBrowser, QSize, QAction)
from PyQt4.Qt import (QDialog, QWidget, QTableWidget, QVBoxLayout, QHBoxLayout,
QGridLayout, QPushButton, QFont, QLabel, QCheckBox, QIcon,
QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox,
QScrollArea, QPixmap, Qt, QAbstractItemView, QTextEdit,
pyqtSignal, QGroupBox, QFrame, QTextBrowser, QSize, QAction)
try:
from calibre.gui2 import QVariant
@@ -581,14 +581,34 @@ class UserPassDialog(QDialog):
self.status=False
self.hide()
class LoopProgressDialog(QProgressDialog):
def LoopProgressDialog(gui,
book_list,
foreach_function,
finish_function,
init_label=_("Fetching metadata for stories..."),
win_title=_("Downloading metadata for stories"),
status_prefix=_("Fetched metadata for")):
ld = _LoopProgressDialog(gui,
book_list,
foreach_function,
init_label,
win_title,
status_prefix)
# Mac OS X gets upset if the finish_function is called from inside
# the real _LoopProgressDialog class.
# reflect old behavior.
if not ld.wasCanceled():
finish_function(book_list)
class _LoopProgressDialog(QProgressDialog):
'''
ProgressDialog displayed while fetching metadata for each story.
'''
def __init__(self, gui,
book_list,
foreach_function,
finish_function,
init_label=_("Fetching metadata for stories..."),
win_title=_("Downloading metadata for stories"),
status_prefix=_("Fetched metadata for")):
@@ -599,7 +619,6 @@ class LoopProgressDialog(QProgressDialog):
self.setMinimumWidth(500)
self.book_list = book_list
self.foreach_function = foreach_function
self.finish_function = finish_function
self.status_prefix = status_prefix
self.i = 0
self.start_time = datetime.now()
@@ -639,6 +658,7 @@ class LoopProgressDialog(QProgressDialog):
self.foreach_function(book)
except NotGoingToDownload as d:
book['status']=_('Skipped')
book['good']=False
book['showerror']=d.showerror
book['comment']=unicode(d)
@@ -647,8 +667,7 @@ class LoopProgressDialog(QProgressDialog):
except Exception as e:
book['good']=False
book['comment']=unicode(e)
logger.error("Exception: %s:%s"%(book,unicode(e)))
traceback.print_exc()
logger.error("Exception: %s:%s"%(book,unicode(e)),exc_info=True)
self.updateStatus()
self.i += 1
@@ -660,8 +679,6 @@ class LoopProgressDialog(QProgressDialog):
def do_when_finished(self):
self.hide()
# Queues a job to process these books in the background.
self.finish_function(self.book_list)
def time_duration_format(seconds):
"""
@@ -1433,6 +1450,15 @@ class ViewLog(SizePersistedDialog):
self.lineno = None
scrollable = QScrollArea()
scrollcontent = QWidget()
scrollable.setWidget(scrollcontent)
scrollable.setWidgetResizable(True)
self.l.addWidget(scrollable)
self.sl = QVBoxLayout()
scrollcontent.setLayout(self.sl)
## error = (lineno, msg)
for (lineno, error_msg) in errors:
# print('adding label for error:%s: %s'%(lineno, error_msg))
@@ -1443,7 +1469,7 @@ class ViewLog(SizePersistedDialog):
label.setStyleSheet("QLabel { margin-left: 2em; color : blue; } QLabel:hover { color: red; }");
label.setToolTip(_('Click to go to line %s')%lineno)
label.mouseReleaseEvent = partial(self.label_clicked, lineno=lineno)
self.l.addWidget(label)
self.sl.addWidget(label)
# html='<p>'+'</p><p>'.join([ '(lineno: %s) %s'%e for e in errors ])+'</p>'
@@ -1453,7 +1479,7 @@ class ViewLog(SizePersistedDialog):
# self.tb.setHtml(html)
# l.addWidget(self.tb)
self.l.insertStretch(-1)
self.sl.insertStretch(-1)
horz = QHBoxLayout()
+82 -53
View File
@@ -73,7 +73,7 @@ except:
from calibre.library.field_metadata import FieldMetadata
field_metadata = FieldMetadata()
from calibre_plugins.fanficfare_plugin.common_utils import (
set_plugin_icon_resources, get_icon, create_menu_action_unique,
get_library_uuid)
@@ -497,11 +497,11 @@ class FanFicFarePlugin(InterfaceAction):
},"\n".join(url_list))
else:
self.gui.status_bar.show_message(_('Finished Fetching Story URLs from Email.'),3000)
else:
if url_list:
self.add_dialog("\n".join(url_list),merge=False)
else:
else:
msg = _('No Valid Story URLs Found in Unread Emails.')
if reject_list:
msg = msg + '<p>'+(_('(%d Story URLs Skipped, on Rejected URL List)')%len(reject_list))+'</p>'
@@ -509,7 +509,7 @@ class FanFicFarePlugin(InterfaceAction):
msg,
show=True,
show_copy_button=False)
def get_urls_from_page_menu(self,anthology=False):
urltxt = ""
@@ -534,7 +534,7 @@ class FanFicFarePlugin(InterfaceAction):
self.gui.status_bar.show_message(_('Finished Fetching Story URLs from Page.'),3000)
self.restore_cursor()
if url_list:
self.add_dialog("\n".join(url_list),merge=d.anthology,anthology_url=url)
else:
@@ -606,7 +606,7 @@ class FanFicFarePlugin(InterfaceAction):
self.gui.status_bar.show_message(_('Can only UnNew books in library'),
3000)
return
if not self.gui.current_view().selectionModel().selectedRows() :
self.gui.status_bar.show_message(_('No Selected Books to Get URLs From'),
3000)
@@ -632,7 +632,7 @@ class FanFicFarePlugin(InterfaceAction):
suffix='.epub',
dir=tdir)
db.copy_format_to(book['calibre_id'],'EPUB',tmp,index_is_id=True)
unnewtmp = PersistentTemporaryFile(prefix='unnew-%s-'%book['calibre_id'],
suffix='.epub',
dir=tdir)
@@ -656,7 +656,7 @@ class FanFicFarePlugin(InterfaceAction):
if fmt.lower() != 'epub' and db.has_format(book['calibre_id'],fmt,index_is_id=True):
logger.debug("autoconvert remove f:"+fmt)
db.remove_format(book['calibre_id'], fmt, index_is_id=True)#, notify=False
def get_unnew_books_finish(self, book_list, tdir=None):
remove_dir(tdir)
if prefs['autoconvert']:
@@ -787,7 +787,7 @@ class FanFicFarePlugin(InterfaceAction):
self.busy_cursor()
self.gui.status_bar.show_message(_('Fetching Story URLs for Series...'))
# get list from identifiers:url/uri if present, but only if
# it's *not* a valid story URL.
mergeurl = self.get_story_url(db,book_id)
@@ -798,7 +798,7 @@ class FanFicFarePlugin(InterfaceAction):
self.gui.status_bar.show_message(_('Finished Fetching Story URLs for Series.'),3000)
self.restore_cursor()
#print("urlmapfile:%s"%urlmapfile)
# AddNewDialog collects URLs, format and presents buttons.
@@ -958,7 +958,7 @@ class FanFicFarePlugin(InterfaceAction):
init_label=_("Fetching metadata for stories...")
win_title=_("Downloading metadata for stories")
status_prefix=_("Fetched metadata for")
self.gui.status_bar.show_message(status_bar, 3000)
LoopProgressDialog(self.gui,
books,
@@ -1072,7 +1072,7 @@ class FanFicFarePlugin(InterfaceAction):
## Getting metadata from configured column.
custom_columns = self.gui.library_view.model().custom_columns
if ( collision in (CALIBREONLYSAVECOL) and
prefs['savemetacol'] != '' and
prefs['savemetacol'] != '' and
prefs['savemetacol'] in custom_columns ):
savedmeta_book_id = book['calibre_id']
@@ -1081,13 +1081,13 @@ class FanFicFarePlugin(InterfaceAction):
identicalbooks = self.do_id_search(url)
if len(identicalbooks) == 1:
savedmeta_book_id = identicalbooks.pop()
if savedmeta_book_id:
label = custom_columns[prefs['savemetacol']]['label']
savedmetadata = db.get_custom(savedmeta_book_id, label=label, index_is_id=True)
else:
savedmetadata = None
if savedmetadata:
# sets flag inside story so getStoryMetadataOnly won't hit server.
adapter.setStoryMetadata(savedmetadata)
@@ -1123,19 +1123,19 @@ class FanFicFarePlugin(InterfaceAction):
if userpass.status:
adapter.username = userpass.user.text()
adapter.password = userpass.passwd.text()
except exceptions.AdultCheckRequired:
if question_dialog(self.gui, _('Are You an Adult?'), '<p>'+
_("%s requires that you be an adult. Please confirm you are an adult in your locale:")%url,
show_copy_button=False):
adapter.is_adult=True
# let other exceptions percolate up.
story = adapter.getStoryMetadataOnly(get_cover=False)
book['title'] = story.getMetadata('title')
book['author'] = [story.getMetadata('author')]
book['url'] = story.getMetadata('storyUrl')
## Check reject list. Redundant with below for when story
## URL changes, but also kept here to avoid network hit in
## most common case where given url is story url.
@@ -1167,7 +1167,7 @@ class FanFicFarePlugin(InterfaceAction):
book['icon']='rotate-right.png'
book['status'] = _('Skipped')
return
################################################################################################################################################33
book['is_adult'] = adapter.is_adult
@@ -1176,7 +1176,7 @@ class FanFicFarePlugin(InterfaceAction):
book['icon'] = 'plus.png'
book['status'] = _('Add')
if not bgmeta:
# set PI version instead of default.
if 'version' in options:
@@ -1187,7 +1187,7 @@ class FanFicFarePlugin(InterfaceAction):
if prefs['savemetacol'] != '':
# get metadata to save in configured column.
book['savemetacol'] = story.dump_html_metadata()
book['title'] = story.getMetadata("title", removeallentities=True)
book['author_sort'] = book['author'] = story.getList("author", removeallentities=True)
book['publisher'] = story.getMetadata("site")
@@ -1198,7 +1198,7 @@ class FanFicFarePlugin(InterfaceAction):
else:
book['comments']=''
book['series'] = story.getMetadata("series", removeallentities=True)
if story.getMetadataRaw('datePublished'):
book['pubdate'] = story.getMetadataRaw('datePublished').replace(tzinfo=local_tz)
if story.getMetadataRaw('dateUpdated'):
@@ -1207,7 +1207,7 @@ class FanFicFarePlugin(InterfaceAction):
book['timestamp'] = story.getMetadataRaw('dateCreated').replace(tzinfo=local_tz)
else:
book['timestamp'] = None # need *something* there for calibre.
if not merge:# skip all the collision code when d/ling for merging.
if collision in (CALIBREONLY, CALIBREONLYSAVECOL):
book['icon'] = 'metadata.png'
@@ -1334,7 +1334,7 @@ class FanFicFarePlugin(InterfaceAction):
# check make sure incoming is newer.
lastupdated=story.getMetadataRaw('dateUpdated')
logger.debug("OVERWRITE site updated: %s"%lastupdated)
# updated doesn't have time (or is midnight), use dates only.
# updated does have time, use full timestamps.
if (lastupdated.time() == time.min and fileupdated.date() > lastupdated.date()) or \
@@ -1377,19 +1377,19 @@ class FanFicFarePlugin(InterfaceAction):
if not label and k in field_metadata:
label=field_metadata[k]['name']
key='calibre_std_'+k
# if k == 'user_categories':
# value=u', '.join(mi.get(k))
# label=_('User Categories')
if label: # only if it has a human readable name.
if value is None or not book['calibre_id']:
## if existing book, populate existing calibre column
## values in metadata, else '' to hide.
value=''
value=''
book['calibre_columns'][key]={'val':value,'label':label}
#logger.debug("%s(%s): %s"%(label,key,value))
# custom columns
for k, column in self.gui.library_view.model().custom_columns.iteritems():
if k != prefs['savemetacol']:
@@ -1405,7 +1405,7 @@ class FanFicFarePlugin(InterfaceAction):
value=''
book['calibre_columns'][key]={'val':value,'label':label}
# logger.debug("%s(%s): %s"%(label,key,value))
# if still 'good', make a temp file to write the output to.
# For HTML format users, make the filename inside the zip something reasonable.
# For crazy long titles/authors, limit it to 200chars.
@@ -1472,10 +1472,20 @@ class FanFicFarePlugin(InterfaceAction):
htmllog = htmllog + '</table></body></html>'
payload = ([], book_list, options)
self.gui.proceed_question(self.update_error_column,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download ended'), msg,
show_copy_button=False)
# log_viewer_unique_name implemented here: https://github.com/kovidgoyal/calibre/compare/v2.56.0...v2.57.0
if calibre_version >= (2, 57, 0):
self.gui.proceed_question(self.update_error_column,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download ended'), msg,
show_copy_button=False,
log_viewer_unique_name="FanFicFare log viewer")
else:
self.gui.proceed_question(self.update_error_column,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download ended'), msg,
show_copy_button=False)
return
cookiejarfile = PersistentTemporaryFile(suffix='.cookiejar',
@@ -1709,10 +1719,18 @@ class FanFicFarePlugin(InterfaceAction):
do_update_func = self.do_download_list_update
self.gui.proceed_question(do_update_func,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download complete'), msg,
show_copy_button=False)
# log_viewer_unique_name implemented here: https://github.com/kovidgoyal/calibre/compare/v2.56.0...v2.57.0
if calibre_version >= (2, 57, 0):
self.gui.proceed_question(do_update_func,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download complete'), msg,
show_copy_button=False,
log_viewer_unique_name="FanFicFare log viewer")
else:
self.gui.proceed_question(do_update_func,
payload, htmllog,
_('FanFicFare log'), _('FanFicFare download complete'), msg,
show_copy_button=False)
def do_download_merge_update(self, payload):
@@ -1750,6 +1768,7 @@ class FanFicFarePlugin(InterfaceAction):
self.get_epubmerge_plugin().do_merge(tmp.name,
[ x['outfile'] for x in good_list ],
tags=mergebook['tags'],
titleopt=mergebook['title'],
keepmetadatafiles=True,
source=mergebook['url'])
@@ -1939,14 +1958,17 @@ class FanFicFarePlugin(InterfaceAction):
configuration = None
if prefs['allow_custcol_from_ini']:
configuration = get_fff_config(book['url'],options['fileform'])
# meta => custcol[,a|n|r]
# meta => custcol[,a|n|r|n_anthaver,r_anthaver]
# cliches=>\#acolumn,r
for line in configuration.getConfig('custom_columns_settings').splitlines():
if "=>" in line:
(meta,custcol) = map( lambda x: x.strip(), line.split("=>") )
flag='r'
anthaver=False
if "," in custcol:
(custcol,flag) = map( lambda x: x.strip(), custcol.split(",") )
anthaver = 'anthaver' in flag
flag=flag[0] # first char only.
if meta not in book['all_metadata']:
# if double quoted, use as a literal value.
@@ -1968,8 +1990,15 @@ class FanFicFarePlugin(InterfaceAction):
if flag == 'r' or (flag == 'n' and book['added']):
if coldef['datatype'] in ('int','float'): # for favs, etc--site specific metadata.
if 'anthology_meta_list' in book and meta in book['anthology_meta_list']:
# re-split list, strip commas, convert to floats, sum up.
val = sum([ float(x.replace(",","")) for x in val.split(", ") ])
# re-split list, strip commas, convert to floats
items = [ float(x.replace(",","")) for x in val.split(", ") ]
if anthaver:
if items:
val = sum(items) / float(len(items))
else:
val = 0
else:
val = sum(items)
else:
val = unicode(val).replace(",","")
else:
@@ -2042,7 +2071,7 @@ class FanFicFarePlugin(InterfaceAction):
prefs['gencalcover'] == SAVE_YES ## yes, always
or (prefs['gencalcover'] == SAVE_YES_UNLESS_IMG ## yes, unless image.
and book['all_metadata']['cover_image'] not in ('specific','first','default')) ):
cover_generated = False # flag for polish below.
# Yes, should do gencov. Which?
if prefs['calibre_gen_cover'] and HAS_CALGC:
@@ -2055,19 +2084,19 @@ class FanFicFarePlugin(InterfaceAction):
cover_generated = True
elif prefs['plugin_gen_cover'] and 'Generate Cover' in self.gui.iactions:
# plugin, if available.
#logger.debug("Do Generate Cover added:%s gcnewonly:%s"%(book['added'],prefs['gcnewonly']))
# force a refresh if generating cover so complex composite
# custom columns are current and correct
db.refresh_ids([book_id])
gc_plugin = self.gui.iactions['Generate Cover']
setting_name = None
if prefs['allow_gc_from_ini']:
if not configuration: # might already have it from allow_custcol_from_ini
configuration = get_fff_config(book['url'],options['fileform'])
# template => regexp to match => GC Setting to use.
# generate_cover_settings:
# ${category} => Buffy:? the Vampire Slayer => Buffy
@@ -2076,25 +2105,25 @@ class FanFicFarePlugin(InterfaceAction):
# (template,regexp,setting) = map( lambda x: x.strip(), line.split("=>") )
for (template,regexp,setting) in configuration.get_generate_cover_settings():
value = Template(template).safe_substitute(book['all_metadata']).encode('utf8')
print("%s(%s) => %s => %s"%(template,value,regexp,setting))
# print("%s(%s) => %s => %s"%(template,value,regexp,setting))
if re.search(regexp,value):
setting_name = setting
break
if setting_name:
logger.debug("Generate Cover Setting from generate_cover_settings(%s)"%setting_name)
if setting_name not in gc_plugin.get_saved_setting_names():
logger.info("GC Name %s not found, discarding! (check personal.ini for typos)"%setting_name)
setting_name = None
if not setting_name and book['all_metadata']['site'] in prefs['gc_site_settings']:
setting_name = prefs['gc_site_settings'][book['all_metadata']['site']]
logger.debug("Generate Cover Setting from site(%s)"%setting_name)
if not setting_name and 'Default' in prefs['gc_site_settings']:
setting_name = prefs['gc_site_settings']['Default']
logger.debug("Generate Cover Setting from Default(%s)"%setting_name)
if setting_name:
logger.debug("Running Generate Cover with settings %s."%setting_name)
## fetch updated mi object from
@@ -2103,14 +2132,14 @@ class FanFicFarePlugin(InterfaceAction):
realmi = db.get_metadata(book_id, index_is_id=True)
gc_plugin.generate_cover_for_book(realmi,saved_setting_name=setting_name)
cover_generated = True
if cover_generated and prefs['gc_polish_cover'] and \
options['fileform'] == "epub":
# set cover inside epub from calibre's polish feature
from calibre.ebooks.oeb.polish.main import polish, ALL_OPTS
from calibre.utils.logging import Log
from collections import namedtuple
# Couldn't find a better way to get the cover path.
cover_path = os.path.join(db.library_path,
db.path(book_id, index_is_id=True),
@@ -2121,7 +2150,7 @@ class FanFicFarePlugin(InterfaceAction):
opts.update(data)
O = namedtuple('Options', ' '.join(ALL_OPTS.iterkeys()))
opts = O(**opts)
log = Log(level=Log.DEBUG)
outfile = db.format_abspath(book_id,
formmapping[options['fileform']],
@@ -2487,7 +2516,7 @@ class FanFicFarePlugin(InterfaceAction):
def restore_cursor(self):
QApplication.restoreOverrideCursor()
def split_text_to_urls(urls):
# remove dups while preserving order.
dups=set()
+1 -2
View File
@@ -319,8 +319,7 @@ def do_download_for_worker(book,options,merge,notification=lambda x,y:x):
book['comment']=unicode(e)
book['icon']='dialog_error.png'
book['status'] = _('Error')
logger.info("Exception: %s:%s"%(book,unicode(e)))
traceback.print_exc()
logger.info("Exception: %s:%s"%(book,unicode(e)),exc_info=True)
#time.sleep(10)
return book
+21 -90
View File
@@ -474,8 +474,8 @@ add_to_include_subject_tags:,tagsfromtitle.SPLIT,forumtags
## base_xenforoforum reads Published and Updated datetimes from
## Threadmarks if used, or from the posted & updated times of the
## 'first' post if no threadmarks.
datePublished_format:%%Y-%%m-%%d %%H:%%M:%%S
dateUpdated_format:%%Y-%%m-%%d %%H:%%M:%%S
datePublished_format:%%Y-%%m-%%d %%H:%%M
dateUpdated_format:%%Y-%%m-%%d %%H:%%M
## Only take the first X characters of the 'first' post to use as
## the description.
@@ -847,18 +847,6 @@ extracategories:The Sentinel
## this should go in your personal.ini, not defaults.ini.
#is_adult:true
[bdsm-geschichten.net]
## 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
## This site offers no index page so we can either guess the chapter URLs
## by dec/incrementing numbers ('guess') or walk all the chapters in the metadata
## parsing state ('parse'). Since guessing can lead to errors for non-standard
## story URLs, the default is to parse
#find_chapters:guess
[bloodshedverse.com]
## website encoding(s) In theory, each website reports the character
## encoding they use for each page. In practice, some sites report it
@@ -869,6 +857,12 @@ extracategories:The Sentinel
## it has +90% confidence. 'auto' is not reliable.
website_encodings:Windows-1252,ISO-8859-1,auto
## dateUpdate doesn't usually have time, but it does on
## bloodshedverse.com. See
## http://docs.python.org/library/datetime.html#strftime-strptime-behavior
## Note that ini format requires % to be escaped as %%.
dateUpdated_format:%%Y-%%m-%%d %%H:%%M
## Extra metadata that this adapter knows about. See [dramione.org]
## for examples of how to use them.
extra_valid_entries:warnings,reviews
@@ -898,15 +892,6 @@ strip_text_links:true
## Site dedicated to these categories/characters/ships
extracategories:Blood Ties
[buffynfaith.net]
## Site dedicated to these categories/characters/ships
extracategories:Buffy: The Vampire Slayer
## 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.
#is_adult:true
[fanfic.castletv.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -1023,15 +1008,24 @@ cliches_label:Character Cliches
## 'mode'. 'r' to Replace any existing values, 'a' to Add to existing
## value (use with tag-like columns), and 'n' for setting on New books
## only. (Default is 'r'.)
## Literal strings can be set into custom columns using double quotes.
## Each metadata=>column mapping must be on a separate line and each
## needs to have one space at the start of each line.
## 'r_anthaver' and 'n_anthaver' can be used to indicate the same as
## 'r' and 'n' for normal downloads, but to average the metadata for
## the differents story in an anthology before setting in integer and
## float type custom columns. This can be useful for a averrating
## column, for example. Default is to sum the values of all stories,
## and numChapters and numWords are always summed.
#custom_columns_settings:
# cliches=>#acolumn
# themes=>#bcolumn,a
# timeline=>#ccolumn,n
# "FanFiction"=>#collection
# averrating=>#averrating,r_anthaver
[efiction.esteliel.de]
## Site dedicated to these categories/characters/ships
@@ -1262,27 +1256,6 @@ cover_exclusion_regexp:/css/bir.png
[forums.sufficientvelocity.com]
## see [base_xenforoforum]
[grangerenchanted.com]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
## 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.
#is_adult:true
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
extracharacters:Hermione Granger
## Extra metadata that this adapter knows about. See [dramione.org]
## for examples of how to use them.
extra_valid_entries:read,reviews
[hlfiction.net]
## Site dedicated to these categories/characters/ships
extracategories:Highlander
@@ -1530,22 +1503,6 @@ extracategories:Supernatural
extracharacters:Sam,Dean
extraships:Sam/Dean
[scarhead.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
## 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.
#is_adult:true
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[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
@@ -1687,15 +1644,6 @@ readings_label: Readings
## personal.ini, not defaults.ini.
#is_adult:true
[tokra.fandomnet.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
## Site dedicated to these categories/characters/ships
extracategories:Stargate: SG-1
[tolkienfanfiction.com]
## Site dedicated to these categories/characters/ships
extracategories:Lord of the Rings
@@ -1857,6 +1805,10 @@ check_next_chapter:false
#password:yourpassword
[www.ficbook.net]
## 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
[www.fictionalley.org]
## Some sites do not require a login, but do require the user to
@@ -2059,11 +2011,6 @@ cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
## Site dedicated to these categories/characters/ships
extracategories:NCIS
[www.nickngreg.nl]
## Site dedicated to these categories/characters/ships
extracategories:CSI
extraships:Nick Stokes/Greg Sanders
[www.phoenixsong.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -2357,20 +2304,6 @@ extracategories:Andromeda
## Site dedicated to these categories/characters/ships
extracategories:Artemis Fowl
[www.therabidreader.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
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
[www.naiceanilme.net]
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
@@ -2384,8 +2317,6 @@ extracategories:Artemis Fowl
#username:YourName
#password:yourpassword
[overrides]
## It may sometimes be useful to override all of the specific format,
## site and site:format sections in your private configuration. For
+68 -67
View File
@@ -4,6 +4,7 @@
# Translators:
# Ettore Atalan <atalanttore@googlemail.com>, 2014-2015
# ILB, 2014-2016
# jumo, 2016
# Sebastian Keller <Haggard@gmx.de>, 2015
# Simon_Schuette <simonschuette@arcor.de>, 2014-2016
# Simon S, 2015
@@ -12,9 +13,9 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-16 14:33+0000\n"
"Last-Translator: ILB\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-06-22 21:43+0000\n"
"Last-Translator: jumo\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"
@@ -23,11 +24,11 @@ msgstr ""
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "UI Plugin um FanFicition-Stories von verschiedenen Seiten herunterzuladen."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -454,7 +455,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr "Eine URL pro Zeile\n<b>http://...,Notiz</b>\n<b>http://...,Titel von Autor - Notiz</b>"
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr "Bei allen oben angegebenen URL`s diesen Grund anfügen:"
@@ -869,7 +870,7 @@ msgstr "Autor ID"
msgid "Extra Tags"
msgstr "zusätzliche Schlagworte"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Titel"
@@ -882,7 +883,7 @@ msgstr "Story URL"
msgid "Description"
msgstr "Beschreibung"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Autor"
@@ -962,7 +963,7 @@ msgstr "Wenn eine Aktualisierung oder Überschreibung einer existierenden Story
#: config.py:1354
msgid "Save All Errors"
msgstr ""
msgstr "Speicher alle Fehler"
#: config.py:1355
msgid "If unchecked, these errors will not be saved:%s"
@@ -1140,34 +1141,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr "<b>Es ist am sichersten, wenn Sie ein separates eMail-Konto erstellen, dass nur für die Story-Update-Mails genutzt wird. FanFicFare und Calibre können nicht garantieren, dass Schadprogramme nicht an Ihr eMail-Passwort kommen, sobald Sie es eingegeben haben.<br>Benutzen Sie diese Funktion auf eigene Verantwortung.</b>"
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Optionen zum Herunterladen anzeigen"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "Ausgabe-&Format:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Wählen Sie das zu erstellende Ausgabeformat. Kann als Voreinstellung in der Plugin-Konfiguration gesetzt werden."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Calibre-&Metadaten aktualisieren?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr "Metadaten für vorhandene Stories in Calibre von der Web-Seite aktualisieren?\n(Gesetzte Spalten für \"Nur neue\" in der Spalte Registerkarte wird nur für neue Bücher berücksichtigt.)"
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "EPUB Cover aktualisieren?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1232,11 +1233,11 @@ msgid ""
"Requires %s plugin."
msgstr "URLs holen und zum Download-Dialog für Sammelbände gehen.\nErfordert %s Plugin."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Abbrechen"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Passwort"
@@ -1260,84 +1261,88 @@ msgstr "Benutzer:"
msgid "Password:"
msgstr "Passwort:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Metadaten für folgende Stories abrufen..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Metadaten für folgende Stories herunterladen"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Metadaten abgerufen für"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr "- %s geschätzt bis erledigt"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Übersprungen"
#: dialogs.py:692
msgid "%d day"
msgstr "%d Tag"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d Tage"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d Stunde"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d Stunden"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d Minute"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d Minuten"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d Sekunde"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d Sekunden"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "weniger als 1 Sekunde"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Über FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Ausgewählte Bücher von der Liste löschen"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Aktualisierungsmodus:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Welche Art von Aktualisierung ausgeführt werden soll. Eine Standardeinstellung kann in den Plugin Konfigurationen festgelegt werden."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr "Hintergrundmetadaten?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1345,103 +1350,103 @@ msgid ""
" fail."
msgstr "Sammelt Metadaten von Webseiten im Hintergrund.\nWährend eines Updates gibt dies die Kontrolle schneller an Sie zurück, aber Sie werden nicht nach Nutzernamen/Passwörtern oder ihrem Alter gefragt--Geschichten, die diese Daten benötigen, können nicht geladen werden."
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Kommentar"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Sind sie sicher, dass sie dieses Buch von der Liste löschen wollen?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Sind sie sicher, dass sie die ausgewählten Bücher von der Liste löschen wollen?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Notiz"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr "Ablehnungsnotiz auswählen oder bearbeiten."
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "Sind sie sicher, dass sie diese URL von der Liste löschen möchten?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "Sind sie sicher, dass sie die %d ausgewählten URLs von der Liste löschen möchten?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Liste der Bücher zur Ablehnung"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr "FFF merkt sich diese URLs, zeigt die Notiz an und bietet an, diese abzulehnen, wenn Sie diese nochmal versuchen, diese herunterzuladen."
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Entfernt ausgewählte URLs von der Liste"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr "Dies wird für jede der oben angegebenen URLs zusätzlich an die Notiz angefügt, die sie oben angegeben haben."
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "Bücher löschen (inklusive Bücher ohne FanFiction-URLs)?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Die ausgewählten Bücher werden zur URL-Ablehnungsliste hinzugefügt und danach gelöscht."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Suche nach Zeichenfolge im Eingabefeld."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Suche:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Suche"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Groß- und Kleinschreibung"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Suche nach Groß- und Kleinschreibung; behandle Harry, HARRY und harry nicht gleich."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Zurück um einen Fehler zu beheben?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr "Klicke auf eine Fehlermeldung um direkt im Editor auf diese Zeile zu springen:"
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Klicken um zur Zeile %s zu kommen"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Zum Editieren zurückkehren"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Trotzdem sichern"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "E-Mail-Passwort für %s eingeben:"
@@ -1818,10 +1823,6 @@ msgstr "Klicken Sie '<b>Yes</b>' um zu überspringen."
msgid "Story in Series Anthology(%s)."
msgstr "Story in Serien-Sammelband (%s)."
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Übersprungen"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Hinzufügen"
@@ -2116,7 +2117,7 @@ msgstr "FanFiction-Geschichten herunterladen"
#: jobs.py:91
msgid "%d of %d stories finished downloading"
msgstr ""
msgstr "%d von %d der Geschichten sind fertig runtergeladen"
#: jobs.py:103
msgid "Download Results:"
+72 -70
View File
@@ -4,15 +4,17 @@
# Translators:
# Adolfo Jayme Barrientos, 2014
# dario hereñu <magallania@gmail.com>, 2015
# Enrique Medina <medina9304@gmail.com>, 2016
# Jellby <jellby@yahoo.com>, 2014-2016
# Antonio Mireles <antonio@mirelesindependent.com>, 2016
# juanda097 <juanda097@openmailbox.org>, 2016
# JimmXinu <retiefjimm@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-13 22:04+0000\n"
"Last-Translator: Antonio Mireles <antonio@mirelesindependent.com>\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-07-12 05:10+0000\n"
"Last-Translator: Enrique Medina <medina9304@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/calibre/calibre-plugins/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,11 +23,11 @@ msgstr ""
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Complemento de interfaz de usuario para descargar historias de «fanfiction» desde distintos sitios de Internet."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -452,7 +454,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr "Un URL por línea.\n<b>http://...,nota</b>\n<b>http://...,título por autor - nota</b>"
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr "Añadir este motivo a todos los URL añadidos:"
@@ -465,7 +467,7 @@ msgstr "Estas configuraciones proporcionan un control más fino sobre qué metad
#: config.py:673
msgid "personal.ini"
msgstr ""
msgstr "personal.ini"
#: config.py:680 config.py:784 config.py:785
msgid "Edit personal.ini"
@@ -479,17 +481,17 @@ msgstr "FanFicFare ahora incluye búsquedas, código de color y comprobación de
#: config.py:693
msgid "View \"Safe\" personal.ini"
msgstr ""
msgstr "Ver personal.ini \"Seguro\""
#: config.py:698 config.py:775
msgid ""
"View your personal.ini with usernames and passwords removed. For safely "
"sharing your personal.ini settings with others."
msgstr ""
msgstr "Ver sus personal.ini con nombres de usuario y contraseñas eliminadas. Para compartir de forma segura la configuración personal.ini con otros."
#: config.py:704
msgid "defaults.ini"
msgstr ""
msgstr "defaults.ini"
#: config.py:709
msgid ""
@@ -541,7 +543,7 @@ msgstr "Valores predeterminados (%s) (sólo lectura)"
#: config.py:774
msgid "View 'Safe' personal.ini"
msgstr ""
msgstr "Ver personal.ini 'Seguro'"
#: config.py:808
msgid "Calibre Column Entry Names"
@@ -867,7 +869,7 @@ msgstr "ID del autor"
msgid "Extra Tags"
msgstr "Etiquetas adicionales"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Título"
@@ -880,7 +882,7 @@ msgstr "URL de la historia"
msgid "Description"
msgstr "Descripción"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Autor"
@@ -1138,34 +1140,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr "<b>Lo más seguro es crear una cuenta de correo electrónico separada que use sólo para las notificaciones de actualización de historias. FanFicFare y calibre no pueden garantizar que ningún software malicioso pueda obtener la contraseña una vez que la haya introducido.<br>Use esta función bajo su propia responsabilidad.</b>"
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Mostrar opciones de descarga"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "&Formato de salida:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Elija un formato de salida para crear. Puede establecer el predeterminado en la configuración del complemento."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "¿Actualizar los metadatos de calibre?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr "¿Actualizar los metadatos de las historias existentes en calibre a partir del sitio de internet?\n(Las columnas establecidas en «Sólo nuevo» en la pestaña de columnas sólo se cambiarán para libros nuevos)."
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "¿Actualizar portada del archivo EPUB?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1230,11 +1232,11 @@ msgid ""
"Requires %s plugin."
msgstr "Obtener URL e ir a la ventana de descarga para antologías.\nRequiere el complemento %s."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Cancelar"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Contraseña"
@@ -1258,84 +1260,88 @@ msgstr "Usuario:"
msgid "Password:"
msgstr "Contraseña:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "Aceptar"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Obteniendo metadatos para las historias..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Descargando metadatos para las historias"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Metadatos obtenidos para"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr "- aproximadamente %s para terminar"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Omitida"
#: dialogs.py:692
msgid "%d day"
msgstr "%d día"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d días"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d hora"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d horas"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minuto"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minutos"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d segundo"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d segundos"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "menos de 1 segundo"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Acerca de FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Eliminar los libros seleccionados de la lista"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Modo de actualización:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Qué tipo de actualización se realizará. Puede definirse el valor predeterminado en la configuración del complemento."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr "¿Metadatos en segundo plano?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1343,103 +1349,103 @@ msgid ""
" fail."
msgstr "Recopilar metadatos de Internet en un proceso en segundo plano.<br/>Esto le devuelve el control más rápidamente al actualizar, pero no tendrá la posibilidad de introducir contraseñas o especificar si es usted adulto, las historias que requieran esa información fallarán sin más."
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Comentario"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "¿Está seguro de querer eliminar este libro de la lista?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "¿Está seguro de querer eliminar los %d libros seleccionados de la lista?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Nota"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr "Seleccionar o modificar nota de rechazo."
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "¿Está seguro de querer eliminar este URL de la lista?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "¿Está seguro de querer eliminar los %d URL seleccionados de la lista?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Lista de libros para rechazar"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr "FFF recordará estos URL, mostrará una nota y le permitirá rechazarlos si vuelve a intentar descargarlos más adelante."
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Eliminar los URL seleccionados de la lista"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr "Se añadirá a cualquier nota que haya establecido para cada URL anterior."
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "¿Eliminar libros (incluyendo libros sin URL de «fanfiction»)?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Eliminar los libros seleccionados después de añadirlos a la lista de URL rechazados."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Buscar el texto en el cuadro."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Buscar:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Buscar"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Distinguir mayúsculas y minúsculas"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Buscar texto distinguiendo mayúsculas y minúsculas; no considera que Carlos, CARLOS y carlos son todos iguales."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "¿Volver atrás para corregir errores?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr "Pulse en un error más abajo para volver a modificar directamente esa línea:"
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Pulsar para ir a la línea %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Volver a modificar"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Guardar de todas formas"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Introduzca la contraseña de correo electrónico para %s:"
@@ -1816,10 +1822,6 @@ msgstr "Pulse en «<b>Sí</b>» para omitir."
msgid "Story in Series Anthology(%s)."
msgstr "Historia en antología de serie (%s)."
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Omitida"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Añadir"
+118 -118
View File
@@ -6,8 +6,8 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-03 01:15+0000\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-23 01:46+0000\n"
"Last-Translator: Maidur\n"
"Language-Team: Estonian (http://www.transifex.com/calibre/calibre-plugins/language/et/)\n"
"MIME-Version: 1.0\n"
@@ -17,11 +17,11 @@ msgstr ""
"Language: et\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Kasutajaliidese plugin mitmetelt saitidelt 'FanFiction'-juttude allalaadimiseks."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -243,7 +243,7 @@ msgstr "Kontrolli juttude URLi muutumist?"
msgid ""
"Warn you if an update will change the URL of an existing book.\n"
"fanfiction.net URLs will change from http to https silently."
msgstr ""
msgstr "Hoiata, kui uuendamine muudab olemasoleva raamatu URLi.\nSaidi fanfiction.net URLides pannakse http asemele https ilma küsimata."
#: config.py:478
msgid "Search EPUB text for Story URL?"
@@ -262,25 +262,25 @@ msgstr "Järeltöötluse valikud"
#: config.py:487
msgid "Mark added/updated books when finished?"
msgstr ""
msgstr "Märgi lisatud/uuendatud raamatud, kui valmis?"
#: config.py:488
msgid ""
"Mark added/updated books when finished. Use with option below.\n"
"You can also manually search for 'marked:fff_success'.\n"
"'marked:fff_failed' is also available, or search 'marked:fff' for both."
msgstr ""
msgstr "Kui valmis saab, märgi lisatud/uuendatud raamatud. Kasuta koos alloleva valikuga.\nVõid ka käsitsi otsida 'marked:fff_success'.\nsaadaval on ka 'marked:fff_failed', või mõlema jaoks otsi 'marked:fff'."
#: config.py:492
msgid "Show Marked books when finished?"
msgstr ""
msgstr "Näita märgitud raamatuid, kui valmis?"
#: config.py:493
msgid ""
"Show Marked added/updated books only when finished.\n"
"You can also manually search for 'marked:fff_success'.\n"
"'marked:fff_failed' is also available, or search 'marked:fff' for both."
msgstr ""
msgstr "Kui valmis saab, näita ainult märgitud lisatud/uuendatud raamatuid.\nVõid ka käsitsi otsida 'marked:fff_success'.\nsaadaval on ka 'marked:fff_failed', või mõlema jaoks otsi 'marked:fff'."
#: config.py:497
msgid "Smarten Punctuation (EPUB only)"
@@ -324,7 +324,7 @@ msgstr "Võta URLid lõikelaualt?"
#: config.py:530
msgid "Prefill URLs from valid URLs in Clipboard when Adding New."
msgstr ""
msgstr "Uute lisamisel täida URLide väli kehtivate URLidega lõikelaualt."
#: config.py:534
msgid "Default to Update when books selected?"
@@ -334,7 +334,7 @@ msgstr "Vaikesättena uuenda, kui raamatud on valitud?"
msgid ""
"The top FanFicFare plugin button will start Update if\n"
"books are selected. If unchecked, it will always bring up 'Add New'."
msgstr "FanFicFare'i ülemine plugina nupp käivitab raamatu uuendamise,\nkui raamatuid on valitud. Kui märgistamata, siis avab see alati akna 'Ava uus'."
msgstr "FanFicFare'i ülemine plugina nupp käivitab raamatu uuendamise,\nkui raamatuid on valitud. Kui märgistamata, siis avab see alati akna 'Lisa uus'."
#: config.py:539
msgid "Keep 'Add New from URL(s)' dialog on top?"
@@ -393,11 +393,11 @@ msgstr "Muuda loendit URLidest, millest FanFicFare automaatselt keeldub."
#: config.py:572 config.py:646
msgid "Add Reject URLs"
msgstr ""
msgstr "Lisa keeldutavaid URLe"
#: config.py:573
msgid "Add additional URLs to Reject as text."
msgstr ""
msgstr "Lisa keeldutavaid URLe teksti kujul."
#: config.py:577
msgid "Edit Reject Reasons List"
@@ -413,11 +413,11 @@ msgstr "Keeldu kinnitust küsimata?"
#: config.py:583
msgid "Always reject URLs on the Reject List without stopping and asking."
msgstr ""
msgstr "Alati keeldu keeldumiste loendis olevatest URLidest ilma peatumata ja kinnitust küsimata."
#: config.py:620
msgid "Edit Reject URLs List"
msgstr ""
msgstr "Muuda keeldutavate URLide loendit"
#: config.py:634
msgid "Reject Reasons"
@@ -425,11 +425,11 @@ msgstr "Keeldumise põhjused"
#: config.py:635
msgid "Customize Reject List Reasons"
msgstr ""
msgstr "Kohanda keeldumise põhjuseid"
#: config.py:644
msgid "Reason why I rejected it"
msgstr ""
msgstr "Põhjus, miks ma sellest keeldusin"
#: config.py:644
msgid "Title by Author"
@@ -448,16 +448,16 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr ""
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr ""
msgstr "Lisa see põhjus kõikidele lisatavatele URLidele:"
#: config.py:666
msgid ""
"These settings provide more detailed control over what metadata will be "
"displayed inside the ebook as well as let you set %(isa)s and %(u)s/%(p)s "
"for different sites."
msgstr ""
msgstr "Need valikud pakuvad üksikasjalikumat kontrolli e-raamatus näidatavate metaandmete üle, samuti lasevad määrata erinevatel saitidel sätted %(isa)s ja %(u)s/%(p)s."
#: config.py:673
msgid "personal.ini"
@@ -510,7 +510,7 @@ msgstr ""
#: config.py:729
msgid "Pass Calibre Columns into FanFicFare on Update/Overwrite"
msgstr ""
msgstr "Uuendamisel/Ülekirjutamisel edasta Calibre veerud FanFicFare'i"
#: config.py:742
msgid ""
@@ -541,7 +541,7 @@ msgstr "Näita faili personal.ini 'turvalist versiooni'"
#: config.py:808
msgid "Calibre Column Entry Names"
msgstr ""
msgstr "Calibre veergude kirjete nimed"
#: config.py:809
msgid "Label (entry_name)"
@@ -635,7 +635,7 @@ msgstr ""
#: config.py:937
msgid "Generate Calibre Cover:"
msgstr ""
msgstr "Loo Calibre kaanepilt:"
#: config.py:964
msgid "Plugin %(gc)s"
@@ -719,7 +719,7 @@ msgid ""
"These settings provide integration with the %(cp)s Plugin. %(cp)s can "
"automatically update custom columns with page, word and reading level "
"statistics. You have to create and configure the columns in %(cp)s first."
msgstr ""
msgstr "Need sätted pakuvad lõimimist pluginaga %(cp)s. %(cp)s suudab kohandatud veerge automaatselt uuendada lehekülgede, sõnade ja loetuse taseme statistikaga. Kõigepealt pead need veerud pluginas %(cp)s looma ja seadistama."
#: config.py:1102
msgid ""
@@ -729,7 +729,7 @@ msgstr ""
#: config.py:1108
msgid "Which column and algorithm to use are configured in %(cp)s."
msgstr ""
msgstr "Kasutatav veerg ja algoritm on seadistatav pluginas %(cp)s."
#: config.py:1118
msgid ""
@@ -863,7 +863,7 @@ msgstr "Autori ID"
msgid "Extra Tags"
msgstr "Lisasildid"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Pealkiri"
@@ -876,7 +876,7 @@ msgstr "Jutu URL"
msgid "Description"
msgstr "Kirjeldus"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Autor"
@@ -927,11 +927,11 @@ msgstr "Ainult uus"
msgid ""
"Write to %s(%s) only for new\n"
"books, not updates to existing books."
msgstr ""
msgstr "Kirjuta veergu %s(%s) ainult uute raamatute puhul,\nmitte olemasolevate raamatute uuendamisel."
#: config.py:1330
msgid "Allow %(ccset)s from %(pini)s to override"
msgstr "Luba sättel %(ccset)s failis %(pini)s alistada"
msgstr "Luba faili %(pini)s sättel %(ccset)s alistada"
#: config.py:1331
msgid ""
@@ -1025,7 +1025,7 @@ msgstr ""
msgid ""
"Write to %s only for new\n"
"books, not updates to existing books."
msgstr ""
msgstr "Kirjuta veergu %s ainult uute raamatute puhul,\nmitte olemasolevate raamatute uuendamisel."
#: config.py:1432
msgid "Other Standard Column Options"
@@ -1044,7 +1044,7 @@ msgid ""
"These settings will allow FanFicFare to fetch story URLs from your email "
"account. It will only look for story URLs in unread emails in the folder "
"specified below."
msgstr ""
msgstr "Need sätted võimaldavad FanFicFarel juttude URLe hankida sinu e-posti kontolt. See otsib juttude URLe ainult allpool määratletud kaustas lugemata kirjadest."
#: config.py:1460
msgid "IMAP Server Name"
@@ -1134,34 +1134,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Näita allalaadimise valikuid"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "Väljundformaat:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr ""
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Uuenda Calibre metaandmeid?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr ""
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "Uuenda EPUBi kaant?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1226,11 +1226,11 @@ msgid ""
"Requires %s plugin."
msgstr "Hangi URLid ja mine antoloogia allalaadimise dialoogile.\nVajalik on plugin %s."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Loobu"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Parool"
@@ -1254,84 +1254,88 @@ msgstr "Kasutaja:"
msgid "Password:"
msgstr "Parool:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Juttude metaandmete tõmbamine..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Juttude metaandmete allalaadimine"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr ""
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - umbes %s lõpuni"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Jäeti vahele"
#: dialogs.py:692
msgid "%d day"
msgstr "%d päev"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d päeva"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d tund"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d tundi"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minut"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minutit"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d sekund"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d sekundit"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "alla 1 sekundi"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Teave plugina FanFicFare kohta"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Eemalda valitud raamatud loendist"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Uuendamise režiim:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Mis sorti uuendus teha? Võib seada vaikesätted plugina seadistusest."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr "Taustal metaandmed?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1339,103 +1343,103 @@ msgid ""
" fail."
msgstr "Korja saitidelt metaandmed taustal töötavas protsessis.<br />See annab uuendades sulle kontrolli tagasi varem, kuid sult ei küsita kasutajanime/paroole ega seda, kas sa oled täisealine -- neid vajavad jutud lihtsalt ebaõnnestuvad."
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Kommentaar"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Kas oled kindel, et tahad selle raamatu loendist eemaldada?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Kas oled kindel, et tahad %d valitud raamatut loendist eemaldada?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Märge"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr ""
msgstr "Vali või muuda keeldumise märget."
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "Kas oled kindel, et tahad selle URLi loendist eemaldada?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "Kas oled kindel, et tahad %d valitud URL loendist eemaldada?"
msgstr "Kas oled kindel, et tahad %d valitud URLi loendist eemaldada?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr ""
msgstr "Keeldutavate raamatute loend"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr ""
msgstr "FFF jätab need URLid meelde ning hilisemal nende allalaadimise uuesti proovimisel näitab märget ja pakub nendest keelduda."
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Eemalda valitud URL-id loendist"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr ""
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "Kustutada raamatud (k.a ilma FanFiction URLita raamatud)?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Kustuta valitud raamatud pärast nende lisamist Keeldutud URLide loendisse."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Otsi muutmise kastist mingit teksti."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Leia:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Leia"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Tõstutundlik"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Otsi suurtähetundlikke sõnesid; ära käsitle sõnesid Harry, HARRY ja harry samadena."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Mine tagasi vigu parandama?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr ""
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Klõpsa, et minna reale %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr ""
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Salvesta ikkagi"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Sisesta e-posti kasutaja %s parool:"
@@ -1445,7 +1449,7 @@ msgstr "FanFicFare"
#: fff_plugin.py:132
msgid "Download FanFiction stories from various web sites"
msgstr "Laadi erinevatelt veebisaitidelt alla 'FanFiction'-jutte"
msgstr "Laadi erinevatelt veebisaitidelt alla FanFiction-jutte"
#: fff_plugin.py:293
msgid "&Download from URLs"
@@ -1537,7 +1541,7 @@ msgstr "Seadme vaatest ei saa lugemisloendeid uuendada"
#: fff_plugin.py:437
msgid "No Selected Books to Update Reading Lists"
msgstr ""
msgstr "Lugemisloendite uuendamiseks pole raamatuid valitud"
#: fff_plugin.py:447
msgid "FanFicFare Email Settings are not configured."
@@ -1557,7 +1561,7 @@ msgstr "Lõpetati e-kirjast juttude URLide hankimine."
#: fff_plugin.py:507
msgid "(%d Story URLs Skipped, on Rejected URL List)"
msgstr ""
msgstr "(%d jutu URLi jäeti vahele, keeldutavate URLide loendis)"
#: fff_plugin.py:508
msgid "Get Story URLs from Email"
@@ -1581,7 +1585,7 @@ msgstr "Antud lehelt ei leitud jutu URLe."
#: fff_plugin.py:558 fff_plugin.py:611
msgid "No Selected Books to Get URLs From"
msgstr ""
msgstr "Pole valitud ühtki raamatut, millest URLe saada"
#: fff_plugin.py:576
msgid "Collecting URLs for stories..."
@@ -1682,7 +1686,7 @@ msgstr ""
msgid ""
"There are %d stories in the current anthology that are <b>not</b> going to "
"be kept if you go ahead."
msgstr ""
msgstr "Praeguses antoloogias on %d juttu, mida <b>ei</b> jäeta alles, kui jätkad."
#: fff_plugin.py:847
msgid "Story URLs that will be removed:"
@@ -1730,11 +1734,11 @@ msgstr ""
#: fff_plugin.py:957
msgid "Started fetching metadata for %s stories."
msgstr ""
msgstr "Alustati %s jutu metaandmete hankimist."
#: fff_plugin.py:971
msgid "No valid story URLs entered."
msgstr ""
msgstr "Pole sisestatud ühtki kehtivat jutu URLi."
#: fff_plugin.py:981 fff_plugin.py:987
msgid "Reject URL?"
@@ -1742,7 +1746,7 @@ msgstr "Keeldu URList?"
#: fff_plugin.py:988 fff_plugin.py:1006
msgid "<b>%s</b> is on your Reject URL list:"
msgstr ""
msgstr "<b>%s</b> on sinu keeldutavate URLide loendis:"
#: fff_plugin.py:990
msgid "Click '<b>Yes</b>' to Reject."
@@ -1754,19 +1758,19 @@ msgstr "Ikkagi allalaadimiseks klõpsa '<b>Ei</b>'."
#: fff_plugin.py:993
msgid "Story on Reject URLs list (%s)."
msgstr ""
msgstr "Jutt on keeldutavate URLide loendis (%s)."
#: fff_plugin.py:996
msgid "Rejected"
msgstr ""
msgstr "Keelduti"
#: fff_plugin.py:999
msgid "Remove Reject URL?"
msgstr ""
msgstr "Eemaldada keeldutav URL?"
#: fff_plugin.py:1005
msgid "Remove URL from Reject List?"
msgstr ""
msgstr "Kas eemaldada URL keeldumiste loendist?"
#: fff_plugin.py:1008
msgid "Click '<b>Yes</b>' to remove it from the list,"
@@ -1796,7 +1800,7 @@ msgstr "Kas jätta jutt vahele?"
#: fff_plugin.py:1157
msgid "Skip Anthology Story?"
msgstr "Kas jätta antoloogia jutt vahele="
msgstr "Kas jätta antoloogia jutt vahele?"
#: fff_plugin.py:1158
msgid ""
@@ -1810,11 +1814,7 @@ msgstr "Vahelejätmiseks klõpsa '<b>Jah</b>'."
#: fff_plugin.py:1162
msgid "Story in Series Anthology(%s)."
msgstr ""
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Jäeti vahele"
msgstr "Jutt antoloogia sarjas(%s)."
#: fff_plugin.py:1178
msgid "Add"
@@ -1846,7 +1846,7 @@ msgstr "Muuda jutu URLi?"
msgid ""
"<b>%s</b> by <b>%s</b> is already in your library with a different source "
"URL:"
msgstr ""
msgstr "<b>%s</b>, autoriga <b>%s</b> on erineva lähte-URLiga sinu kogus juba olemas:"
#: fff_plugin.py:1274
msgid "In library: <a href=\"%(liburl)s\">%(liburl)s</a>"
@@ -1858,7 +1858,7 @@ msgstr "Uus URL: <a href=\"%(newurl)s\">%(newurl)s</a>"
#: fff_plugin.py:1276
msgid "Click '<b>Yes</b>' to update/overwrite book with new URL."
msgstr ""
msgstr "Raamatu uuendamiseks/ülekirjutamiseks uue URLiga klõpsa '<b>Jah</b>'."
#: fff_plugin.py:1277
msgid "Click '<b>No</b>' to skip updating/overwriting this book."
@@ -1872,17 +1872,17 @@ msgstr "Alla laadida uue raamatuna?"
msgid ""
"<b>%s</b> by <b>%s</b> is already in your library with a different source "
"URL."
msgstr ""
msgstr "<b>%s</b>, autoriga <b>%s</b> on erineva lähte-URLiga sinu kogus juba olemas."
#: fff_plugin.py:1288
msgid ""
"You chose not to update the existing book. Do you want to add a new book "
"for this URL?"
msgstr ""
msgstr "Sa otsustasid olemasolevat raamatut mitte uuendada. Kas tahad sellele URLile lisada uue raamatu?"
#: fff_plugin.py:1290
msgid "Click '<b>Yes</b>' to a new book with new URL."
msgstr ""
msgstr "Uue URLiga uue raamatu jaoks klõpsa '<b>Jah</b>'."
#: fff_plugin.py:1291
msgid "Click '<b>No</b>' to skip URL."
@@ -1890,7 +1890,7 @@ msgstr "URLi vahelejätmiseks klõpsa '<b>Ei</b>'."
#: fff_plugin.py:1297
msgid "Update declined by user due to differing story URL(%s)"
msgstr ""
msgstr "Kasutaja keeldus uuendamisest erineva jutu URLi tõttu(%s)"
#: fff_plugin.py:1300
msgid "Different URL"
@@ -1956,7 +1956,7 @@ msgstr "Viga metaandmete uuendamisel"
msgid ""
"An error has occurred while FanFicFare was updating calibre's metadata for "
"<a href='%s'>%s</a>."
msgstr ""
msgstr "Esines viga, kui FanFicFare uuendas raamatul <a href='%s'>%s</a> calibre metaandmeid."
#: fff_plugin.py:1540
msgid "The ebook has been updated, but the metadata has not."
@@ -2014,7 +2014,7 @@ msgstr "%s raamatu ühendamine."
#: fff_plugin.py:1767
msgid "FanFicFare Adding/Updating books."
msgstr ""
msgstr "FanFicFare raamatute Lisamine/Uuendamine."
#: fff_plugin.py:1774
msgid "Updating calibre for FanFiction stories..."
@@ -2070,7 +2070,7 @@ msgstr ""
#: fff_plugin.py:2238
msgid "Same story already included."
msgstr ""
msgstr "Sama jutt on juba olemas."
#: fff_plugin.py:2298
msgid "No story URL found."
@@ -2110,7 +2110,7 @@ msgstr "FanFiction-juttude allalaadimine"
#: jobs.py:91
msgid "%d of %d stories finished downloading"
msgstr ""
msgstr "%d / %d jutu allalaadimine valmis"
#: jobs.py:103
msgid "Download Results:"
@@ -2130,7 +2130,7 @@ msgstr "Alustati allalaadimist..."
#: jobs.py:230
msgid "Download %s completed, %s chapters."
msgstr ""
msgstr "%s-faili allalaadimine valmis, %s peatükki."
#: jobs.py:255
msgid "Already contains %d chapters. Reuse as is."
@@ -2138,4 +2138,4 @@ msgstr "Juba sisaldab %d peatükki. Taaskasutatakse nagu on."
#: jobs.py:278
msgid "Update %s completed, added %s chapters for %s total."
msgstr ""
msgstr "%s-faili uuendamine valmis; lisati %s peatükki, nüüd on neid kokku %s."
+67 -66
View File
@@ -4,6 +4,7 @@
# Translators:
# Xotes <alois.glibert@gmail.com>, 2015
# Franck, 2015
# J M <JimmXinuTwo@xinu.nu>, 2016
# Ptit Prince <leporello1791@gmail.com>, 2014-2016
# Piconcely Yoann <yoanncoolazz@gmail.com>, 2015
# sengian <sengian1@gmail.com>, 2016
@@ -12,9 +13,9 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-17 07:52+0000\n"
"Last-Translator: sengian <sengian1@gmail.com>\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-06-01 00:19+0000\n"
"Last-Translator: J M <JimmXinuTwo@xinu.nu>\n"
"Language-Team: French (http://www.transifex.com/calibre/calibre-plugins/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -23,11 +24,11 @@ msgstr ""
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Greffon à interface utilisateur pour télécharger des récits FanFiction de différents sites."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -208,7 +209,7 @@ msgid ""
"%(cmplt)s and %(inprog)s tags will be still be updated, if known.\n"
"%(lul)s tags will be updated if %(lus)s in %(is)s.\n"
"(If Tags is set to 'New Only' in the Standard Columns tab, this has no effect.)"
msgstr "Les étiquettes existantes seront gardées et toutes les nouvelles étiquettes ajoutées.\nLes étiquettes %(cmplt)s et %(inprog) seront quand même mise à jour, si connues.\nLes étiquettes %(lul)s seront mises à jour si %(lus)s dans %(is)s.\n(Si les étiquettes sont définies à 'Nouveau uniquement\" dans l'onglet colonnes standards, ceci n'a pas d'effet.)"
msgstr "Les étiquettes existantes seront gardées et toutes les nouvelles étiquettes ajoutées.\nLes étiquettes %(cmplt)s et %(inprog)s seront quand même mise à jour, si connues.\nLes étiquettes %(lul)s seront mises à jour si %(lus)s dans %(is)s.\n(Si les étiquettes sont définies à 'Nouveau uniquement\" dans l'onglet colonnes standards, ceci n'a pas d'effet.)"
#: config.py:458
msgid "Force Author into Author Sort?"
@@ -454,7 +455,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr "Une URL par ligne : \n<b>http://...,note</b>\n<b>http://...,titre par auteur - note</b>"
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr "Ajouter cette raison pour toutes les URLs ajoutée : "
@@ -869,7 +870,7 @@ msgstr "ID de l'auteur"
msgid "Extra Tags"
msgstr "Etiquettes additionnelles"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Titre"
@@ -882,7 +883,7 @@ msgstr "URL du récit"
msgid "Description"
msgstr "Description"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Auteur"
@@ -1140,34 +1141,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr "<b>Il est plus sûr de créer une compte courriel séparé que vous utilisez uniquement pour les notifications de mise à jour d'histoires. FanFicFare et calibre ne peuvent garantir qu'un code malveillant s'empare de votre mot de passe de courriel une fois que vous l'avez entré. <br>Utilisez cette fonctionnalité à vos propres risques.</b>"
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Afficher les options de téléchargement"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "&Format de sortie :"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Choisir le format de sortie à créer. Peut être réglé par défaut depuis la configuration du greffon."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Mettre à jour les &métadonnées calibre ?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr "Mettre à jour les métadonnées pour les récits existants dans calibre depuis le site web ?\n(Les colonnes définies à \"Nouveau uniquement\" dans les étiquettes de colonne seront uniquement définies pour les nouveaux livres.)"
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "Mettre à jour la couverture de l'ePub ?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1232,11 +1233,11 @@ msgid ""
"Requires %s plugin."
msgstr "Obtenir les URLs et se rendre dans la boîte de dialogue pour le téléchargement d'une Anthologie.\nRequiert le greffon %s."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Annuler"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Mot de passe"
@@ -1260,84 +1261,88 @@ msgstr "Utilisateur :"
msgid "Password:"
msgstr "Mot de passe :"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Occupé à rechercher des métadonnées pour les récits..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Téléchargement des métadonnées pour les récits"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Métadonnées recherchées pour"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - %s prévu jusqu'à la fin"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Ignoré"
#: dialogs.py:692
msgid "%d day"
msgstr "%d jour"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d jours"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d heure"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d heures"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minute"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minutes"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d seconde"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d secondes"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "moins d'1 seconde"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Á propos de FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Retirer les livres sélectionnés de la liste"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Mode de mise à jour : "
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Quel type de mise à jour à effectuer. Peut être réglé par défaut dans la configuration du greffon."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr "Métadonnées d'Arrière-Plan ?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1345,103 +1350,103 @@ msgid ""
" fail."
msgstr "Collecter les Métadonnées à partir de sites dans un processus d'Arrière-Plan. <br /> Ceci vous redonne le contrôle plus rapidement pendant la mise à jour, mais vous ne serez interrogé pour des noms d'utilisateur/mots de passe ou si vous êtes sur une histoire pour adulte qui en à besoin celles-ci échoueront."
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Commentaire"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Êtes-vous sûr de vouloir retirer ce livre de la liste ?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Êtes-vous sûr de vouloir retirer le livre sélectionné %d de la liste ?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Note"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr "Sélectionner ou éditer la note de rejet"
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "Êtes-vous sûr de vouloir retirer cette URL de la liste ?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "Êtes-vous sûr de vouloir retirer les URLs sélectionnées %d de la liste ?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Liste des livres à rejetter"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr "FFF se souviendra de ces URLs, affichera la note et proposera de rejetter celles-ci si vous essayer de les télécharger à nouveau par après."
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Retire les URLs sélectionnées de la liste"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr "Ceci sera ajouté à n'importe quelle note que vous avez composée pour chaque URL ci-dessus."
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "Supprimer les livres (incluant les livres sans URL(s) de FanFiction) ?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Supprime les livres sélectionnés après les avoir ajoutés à la liste des URLs rejetées."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Rechercher la chaîne dans la boîte d'édition."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Trouver :"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Trouver"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Sensible à la casse"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Recherche pour les chaînes sensibles à la casse ; ne traite pas Harry, HARRY et harry de la même façon."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Retour en arrière pour corriger les erreurs ?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr "Cliquez sur une erreur ci-dessous pour modifier directement sur cette ligne :"
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Cliquer pour aller à la ligne %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Retourner à l'Édition"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Enregistrer quand même"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Entrer le mot de passe du compte email pour %s :"
@@ -1818,10 +1823,6 @@ msgstr "Cliquer '<b>Oui</b>' pour ignorer."
msgid "Story in Series Anthology(%s)."
msgstr "Récit dans la série Anthologie(%s)."
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Ignoré"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Ajouter"
+63 -63
View File
@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -15,11 +15,11 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr ""
#: __init__.py:116
#: __init__.py:122
msgid "Path to the calibre library. Default is to use the path stored in the settings."
msgstr ""
@@ -414,7 +414,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr ""
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr ""
@@ -770,7 +770,7 @@ msgstr ""
msgid "Extra Tags"
msgstr ""
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr ""
@@ -783,7 +783,7 @@ msgstr ""
msgid "Description"
msgstr ""
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr ""
@@ -1008,33 +1008,33 @@ msgstr ""
msgid "<b>It's safest if you create a separate email account that you use only for your story update notices. FanFicFare and calibre cannot guarantee that malicious code cannot get your email password once you've entered it. <br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr ""
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr ""
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid "Choose output format to create. May set default from plugin configuration."
msgstr ""
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr ""
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr ""
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr ""
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid "Update book cover image from site or defaults (if found) <i>inside</i> the EPUB when EPUB is updated."
msgstr ""
@@ -1093,11 +1093,11 @@ msgid ""
"Requires %s plugin."
msgstr ""
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr ""
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr ""
@@ -1121,179 +1121,183 @@ msgstr ""
msgid "Password:"
msgstr ""
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr ""
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr ""
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr ""
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr ""
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr ""
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr ""
#: dialogs.py:692
msgid "%d day"
msgstr ""
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr ""
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr ""
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr ""
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr ""
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr ""
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr ""
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr ""
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr ""
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr ""
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr ""
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr ""
#: dialogs.py:809
#: dialogs.py:826
msgid "What sort of update to perform. May set default from plugin configuration."
msgstr ""
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr ""
#: dialogs.py:832
#: dialogs.py:849
msgid "Collect Metadata from sites in a Background process.<br />This returns control to you quicker while updating, but you won't be asked for username/passwords or if you are an adult--stories that need those will just fail."
msgstr ""
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr ""
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr ""
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr ""
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr ""
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr ""
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr ""
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr ""
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr ""
#: dialogs.py:1070
#: dialogs.py:1087
msgid "FFF will remember these URLs and display the note and offer to reject them if you try to download them again later."
msgstr ""
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr ""
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr ""
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr ""
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr ""
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr ""
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr ""
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr ""
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr ""
#: dialogs.py:1289
#: dialogs.py:1306
msgid "Search for case sensitive string; don't treat Harry, HARRY and harry all the same."
msgstr ""
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr ""
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr ""
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr ""
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr ""
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr ""
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr ""
@@ -1661,10 +1665,6 @@ msgstr ""
msgid "Story in Series Anthology(%s)."
msgstr ""
#: fff_plugin.py:1168
msgid "Skipped"
msgstr ""
#: fff_plugin.py:1178
msgid "Add"
msgstr ""
+72 -71
View File
@@ -3,13 +3,14 @@
#
# Translators:
# Andreas Dreyer Hysing <andreashysing@gmail.com>, 2016
# John Henningsen <henningsen.teknikk@gmail.com>, 2016
# Kurt-Håkon Eilertsen <kurt@kheds.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-03-29 09:06+0000\n"
"Last-Translator: Kovid Goyal <kovid@kovidgoyal.net>\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-05-08 12:41+0000\n"
"Last-Translator: John Henningsen <henningsen.teknikk@gmail.com>\n"
"Language-Team: Norwegian Bokmål (http://www.transifex.com/calibre/calibre-plugins/language/nb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,11 +19,11 @@ msgstr ""
"Language: nb\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Grensesnitt plugin for nedlasting av fanfictionhistorier fra forskjellige nettsider."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -449,7 +450,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr "En URL per linje:\n<b>http://...,note</b>\n<b>http://...,title by author - note</b>"
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr "Legg til denne grunnen for alle URLer lagt til:"
@@ -462,7 +463,7 @@ msgstr "Disse innstillingene tilbyr mer detaljert kontroll over hvilke metadata
#: config.py:673
msgid "personal.ini"
msgstr ""
msgstr "personal.ini"
#: config.py:680 config.py:784 config.py:785
msgid "Edit personal.ini"
@@ -476,7 +477,7 @@ msgstr "FanFicFare tilbyr nå søk, fargekoding og feilsjekking for redigering i
#: config.py:693
msgid "View \"Safe\" personal.ini"
msgstr ""
msgstr "Vis \"Safe\" personal.ini"
#: config.py:698 config.py:775
msgid ""
@@ -486,7 +487,7 @@ msgstr ""
#: config.py:704
msgid "defaults.ini"
msgstr ""
msgstr "defaults.ini"
#: config.py:709
msgid ""
@@ -500,7 +501,7 @@ msgstr "Vis standardinstillinger"
#: config.py:721
msgid "Calibre Columns"
msgstr ""
msgstr "Calibre kolonner"
#: config.py:728
msgid ""
@@ -538,7 +539,7 @@ msgstr "Pluginstandardinstillinger (%s) (kun lese)"
#: config.py:774
msgid "View 'Safe' personal.ini"
msgstr ""
msgstr "Vis 'Safe' personal.ini"
#: config.py:808
msgid "Calibre Column Entry Names"
@@ -864,7 +865,7 @@ msgstr "Forfatter ID"
msgid "Extra Tags"
msgstr "Ekstra merkelapper"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Tittel"
@@ -877,7 +878,7 @@ msgstr "Historieurl"
msgid "Description"
msgstr "Beskrivelse"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Forfatter"
@@ -957,7 +958,7 @@ msgstr "Når en oppdatering eller overskriving av en eksisterende historie feile
#: config.py:1354
msgid "Save All Errors"
msgstr ""
msgstr "Lagre alle feil"
#: config.py:1355
msgid "If unchecked, these errors will not be saved:%s"
@@ -1135,34 +1136,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr "<b>Det trygges å opprette en separat epostkonto som du kan bruke for dine historie oppdateringsvarsler. FanFicFare og Calibre kan ikke garantere at ondartet kode ikke kan få tak i passordet på eposten når du har oppgitt det. <br/> Bruk denne funksjonen på egen risiko. </b>"
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Vis nedlastningsvalg"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "Resultat &format:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Velg resultat format for å opprette. Dette kan sette standar for plugin konfigurasjonen."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Oppdater Calibre &Metadata?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr "Oppdater metadata for eksisterende historier i Calibre fra nettside?\n(Kolonner satt til 'Bare nye' i kolonnefanen vil automatisk settes til nye bøker.)"
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "Oppdatere EPUB forside?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1227,11 +1228,11 @@ msgid ""
"Requires %s plugin."
msgstr "Hent URLer og gå til dialogen for antologi nedlastning.\nKrever %s plugin."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Avbryt"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Passord"
@@ -1255,84 +1256,88 @@ msgstr "Bruker:"
msgid "Password:"
msgstr "Passord:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Henter metadata for historier..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Nedlastning av metadata for historier"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Henter metadata for"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr "- %s estimert til ferdig"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Hoppet over"
#: dialogs.py:692
msgid "%d day"
msgstr "%d dag"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d dager"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d time"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d timer"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minutt"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minutter"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d sekunder"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d sekunder"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "mindre enn et sekund"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Om FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Fjern valgte bøker fra listen"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Oppdateringsmodus:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Hvilken oppdatering for handling. Kan sette standard fra pluginkonfigurasjon."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr "Bakrunnsmetadata?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1340,103 +1345,103 @@ msgid ""
" fail."
msgstr "Samle metadata fra siden i en bakgrunnsprosess. <br /> Dette returnerer kontroll til deg fortere ved oppdatering, men du vil ikke bli spurt om brukernavn/passord eller er du i voksenhistorier vil prosessen feile."
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Kommentar"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Er du sikker på at du vil fjerne denne boken fra listen?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Er du sikker på at du vil fjerne de valgte %d bøkene fra listen?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Notat"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr "Velg eller rediger avslått notat"
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "Er du sikker på at du vil fjerne denne URLen fra listen?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "Er du sikker på at du vil fjerene de %d valgte URLene fra listen?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Liste med bøker for avslag"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr "FanFicFare vil huske disse URLene and vise notatet og tilby å avslå dem hvis du prøver å laste dem ned igjen senere."
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Fjerene valgte URLer fra lista"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr "Denne vil bil lagt til det notatet du har satt for hver URL over."
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "Slett bøker (inkludert bøker uten fanfiction URLer)?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Slett de valgte bøkene etter å legge dem til avslagsliten for URLer."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Søk etter streng i rediger boksen."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Finn:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Finn"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Case sensistive"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Søk etter rediger størrelse-sensitive strenger: ikke behandle Harry, HARRY og harry som det samme."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Gå tilbake til rett feil?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr "Klikk en feil under for å returnerer til å redigere direkte i den linjen:"
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Klikk for å gå til linje %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Returner til redigering"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Lagre uansett"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Oppgi e-postpassord for %s:"
@@ -1813,10 +1818,6 @@ msgstr "Trykk '<b>Ja</b>' for å hoppe over."
msgid "Story in Series Anthology(%s)."
msgstr "Historie i serie antologien(%s)."
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Hoppet over"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Legg til"
+77 -76
View File
@@ -2,6 +2,7 @@
# Copyright (C) YEAR ORGANIZATION
#
# Translators:
# Alex, 2016
# Nathan Follens, 2015
# Rodolfo_Jadon, 2014-2015
# Volluta <volluta@tutanota.com>, 2015
@@ -9,9 +10,9 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-03-29 09:06+0000\n"
"Last-Translator: Kovid Goyal <kovid@kovidgoyal.net>\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-06-23 02:16+0000\n"
"Last-Translator: Alex\n"
"Language-Team: Dutch (http://www.transifex.com/calibre/calibre-plugins/language/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,11 +21,11 @@ msgstr ""
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "UI-plugin om FanFiction-verhalen van verschillende sites te downloaden."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -451,7 +452,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr ""
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr ""
@@ -866,7 +867,7 @@ msgstr "Auteurs-ID"
msgid "Extra Tags"
msgstr "Extra tags"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Titel"
@@ -879,7 +880,7 @@ msgstr "Verhaal-URL"
msgid "Description"
msgstr "Beschrijving"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Auteur"
@@ -959,7 +960,7 @@ msgstr ""
#: config.py:1354
msgid "Save All Errors"
msgstr ""
msgstr "Sla alle fouten op"
#: config.py:1355
msgid "If unchecked, these errors will not be saved:%s"
@@ -1099,7 +1100,7 @@ msgstr ""
#: config.py:1507
msgid "Mark Emails Read"
msgstr ""
msgstr "Markeer emails gelezen"
#: config.py:1508
msgid ""
@@ -1137,34 +1138,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Downloadopties weergeven"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "Uitvoer &Formaat:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr ""
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Calibre &Metadata bijwerken?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr ""
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr ""
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1229,11 +1230,11 @@ msgid ""
"Requires %s plugin."
msgstr ""
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Annuleren"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Wachtwoord"
@@ -1257,84 +1258,88 @@ msgstr "Gebruiker:"
msgid "Password:"
msgstr "Wachtwoord:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Bezig met ophalen van metadata voor verhalen..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Bezig met downloaden van metadata voor verhalen"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Metadata opgehaald voor"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - %s geschat tot einde"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Overgeslaan"
#: dialogs.py:692
msgid "%d day"
msgstr "%d dag"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d dagen"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d uur"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d uren"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minuut"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minuten"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d seconde"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d seconden"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "minder dan 1 seconde"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Over FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Geselecteerde boeken uit de lijst verwijderen"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Update-modus:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr ""
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr ""
msgstr "Achtergrond metadata?"
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1342,103 +1347,103 @@ msgid ""
" fail."
msgstr ""
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Reageer"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Ben je zeker dat je dit boek uit de lijst wil verwijderen?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Ben je zeker dat je de geselecteerde %d boeken uit de lijst wil verwijderen?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Noot"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr ""
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr ""
msgstr "Weet u zeker dat u deze URL van de lijst wilt verwijderen?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr ""
msgstr "Weet u zeker dat u de %d geselecteerde URLs van de lijst wilt verwijderen?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Lijst met boeken om af te wijzen"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr ""
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr ""
msgstr "Verwijder geselecteerde URLs van de lijst"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr ""
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr ""
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr ""
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr ""
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Zoeken:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Zoeken"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Hoofdlettergevoelig"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr ""
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Terug gaan om fouten te herstellen?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr ""
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Klik om naar lijn %s te gaan"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Terug naar bewerken"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Toch opslaan"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Voer E-mail wachtwoord in voor %s:"
@@ -1452,7 +1457,7 @@ msgstr "FanFiction-verhalen downloaden van verschillende websites"
#: fff_plugin.py:293
msgid "&Download from URLs"
msgstr ""
msgstr "&Download van URLs"
#: fff_plugin.py:295
msgid "Download FanFiction Books from URLs"
@@ -1781,7 +1786,7 @@ msgstr ""
#: fff_plugin.py:1053
msgid "Cannot update non-epub format."
msgstr ""
msgstr "Kan "
#: fff_plugin.py:1128
msgid "Are You an Adult?"
@@ -1815,10 +1820,6 @@ msgstr ""
msgid "Story in Series Anthology(%s)."
msgstr ""
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Overgeslaan"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Toevoegen"
@@ -2041,7 +2042,7 @@ msgstr ""
#: fff_plugin.py:1820
msgid "Adding format to book failed for some reason..."
msgstr ""
msgstr "Het toevoegen "
#: fff_plugin.py:1823 jobs.py:321
msgid "Error"
@@ -2117,7 +2118,7 @@ msgstr ""
#: jobs.py:103
msgid "Download Results:"
msgstr ""
msgstr "Downloadresultaten:"
#: jobs.py:105
msgid "Successful:"
@@ -2133,7 +2134,7 @@ msgstr "Download gestart..."
#: jobs.py:230
msgid "Download %s completed, %s chapters."
msgstr ""
msgstr "Download %s voltooid, %s hoofdstukken."
#: jobs.py:255
msgid "Already contains %d chapters. Reuse as is."
+63 -63
View File
@@ -8,7 +8,7 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-03-29 09:06+0000\n"
"Last-Translator: Kovid Goyal <kovid@kovidgoyal.net>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/calibre/calibre-plugins/language/pt_BR/)\n"
@@ -19,11 +19,11 @@ msgstr ""
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Plugin para transferência de histórias de ficção de vários sites."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -450,7 +450,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr "Uma URL por linha:\n<b>http://...,nota</b>\n<b>http://...,título por autor - nota</b>"
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr "Adicionar essa razão para todas as URLs adicionadas:"
@@ -865,7 +865,7 @@ msgstr "ID do Autor"
msgid "Extra Tags"
msgstr "Etiquetas Extras"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Título"
@@ -878,7 +878,7 @@ msgstr "URL da História"
msgid "Description"
msgstr "Descrição"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Autor"
@@ -1136,34 +1136,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Mostrar Opções de Transferência"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "&Formato de Saída:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Escolha o formato de saída para criar. Pode-se definir o padrão de configuração do plugin."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr "Atualizar &Metadados do Calibre?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr "Atualizar metadados de histórias existentes no Calibre do site?\n(Colunas definidas como 'Apenas Novo' nas abas da coluna será definido apenas para novos livros.)"
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "Atualizar Capa do EPUB?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1228,11 +1228,11 @@ msgid ""
"Requires %s plugin."
msgstr "Obter URLs e ir para a caixa de diálogo de transferência de Antologia.\nÉ necessário o plugin %s."
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Cancelar"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Senha"
@@ -1256,84 +1256,88 @@ msgstr "Usuário:"
msgid "Password:"
msgstr "Senha:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr "Buscando metadados para histórias..."
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr "Transferindo metadados para histórias"
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr "Metadados pesquisados para"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - %s estimado até ser concluído"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Ignorado"
#: dialogs.py:692
msgid "%d day"
msgstr "%d dia"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d dias"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d hora"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d horas"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minuto"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minutos"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d segundo"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d segundos"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "menos de 1 segundo"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr ""
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Remover livros selecionados da lista"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Modo de Atualização:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr "Que tipo de atualização executar. Pode-se definir o padrão da configuração do plugin."
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr ""
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1341,103 +1345,103 @@ msgid ""
" fail."
msgstr ""
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Comentários"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Você deseja realmente remover este livro da lista?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Você deseja realmente remover os %d livros selecionados da lista?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Nota"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr "Selecionar ou editar a nota de rejeição."
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr "Você deseja realmente remover esta URL da lista?"
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr "Você deseja realmente remover as %s URLs selecionadas da lista?"
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr "Lista de Livros para Rejeitar"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr ""
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr ""
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr "Isto será adicionado a qualquer nota que você definiu para cada URL acima."
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr "Apagar Livros (incluindo livros sem URLs de Ficção)?"
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr "Apagar os livros selecionados depois de adicioná-los à lista de URLs Rejeitadas."
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Buscar pela sequência na caixa de edição."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Localizar:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Localizar"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Maiúsculas e minúsculas"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr "Buscar sequência por maiúsculas e minúsculas; não tratar Harry, HARRY e harry todas como sendo iguais."
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr "Voltar para corrigir erros?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr "Clique em um erro abaixo para retornar para edição diretamente naquela linha:"
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Clique para ir para a linha %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Retornar para edição"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Salvar mesmo assim"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Insira o e-mail de senha para %s:"
@@ -1814,10 +1818,6 @@ msgstr "Clique em '<b>Sim</b>' para ignorar."
msgid "Story in Series Anthology(%s)."
msgstr "História na série de antologia (%s)."
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Ignorado"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Adicionar"
+137 -136
View File
@@ -3,13 +3,14 @@
#
# Translators:
# Henrik Mattsson-Mårn <h@reglage.net>, 2016
# J M <JimmXinuTwo@xinu.nu>, 2016
# Jonatan Nyberg <jonatan@autistici.org>, 2016
# Merarom <merarom@yahoo.es>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-04-16 19:19+0000\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-07-19 12:05+0000\n"
"Last-Translator: Jonatan Nyberg <jonatan@autistici.org>\n"
"Language-Team: Swedish (http://www.transifex.com/calibre/calibre-plugins/language/sv/)\n"
"MIME-Version: 1.0\n"
@@ -19,11 +20,11 @@ msgstr ""
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Grafisk gränssnittsinstickprogram för att ladda ner FanFiction historier från diverse platser."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -47,7 +48,7 @@ msgstr "Inställningar för:"
#: common_utils.py:497
msgid "Clear"
msgstr "Ta bort"
msgstr "Rensa"
#: common_utils.py:499
msgid "Clear all settings for this plugin"
@@ -83,7 +84,7 @@ msgstr "Starta om calibre nu."
#: config.py:192
msgid "List of Supported Sites"
msgstr "Lista av stöda platser"
msgstr "Lista av stödda platser"
#: config.py:194
msgid "FAQs"
@@ -153,18 +154,18 @@ msgid ""
"(title, author, URL, tags, custom columns, etc) from the web site. <br "
"/>This sets whether that will default to on or off. <br />Columns set to "
"'New Only' in the column tabs will only be set for new books."
msgstr ""
msgstr "Vid varje hämtning, erbjuder FanFicFare en möjlighet att uppdatera calibres metadata (titel, författare, URL, taggar, anpassade kolumner, osv.) från webbsidan. <br />Detta ställer in det som standard till på eller av. <br />Kolumner satt till \"Endast ny\" i kolumnflikarna kommer endast att ställas in för nya böcker."
#: config.py:432
msgid "Default Update EPUB Cover when Updating EPUB?"
msgstr ""
msgstr "Som standard uppdatera EPUB omslag vid uppdatering av EPUB?"
#: config.py:433
msgid ""
"On each download, FanFicFare offers an option to update the book cover image"
" <i>inside</i> the EPUB from the web site when the EPUB is updated.<br "
"/>This sets whether that will default to on or off."
msgstr ""
msgstr "Vid varje hämtning, erbjuder FanFicFare en möjlighet att uppdatera bokomslagesbilden <i>i</ i> EPUB:en från webbsidan när EPUB uppdateras. <br />Detta ställer in det som standard till på eller av."
#: config.py:437
msgid "Default Background Metadata?"
@@ -192,7 +193,7 @@ msgstr "Ta bort andra format som finns?"
msgid ""
"Check this to automatically delete all other ebook formats when updating an existing book.\n"
"Handy if you have both a Nook(epub) and Kindle(mobi), for example."
msgstr ""
msgstr "Kryssa i det här för att automatiskt ta bort alla andra e-bokformat när du uppdaterar en befintlig bok.\nPraktiskt om du har både en Nook (EPUB) och Kindle (MOBI), till exempel."
#: config.py:453
msgid "Keep Existing Tags when Updating Metadata?"
@@ -208,7 +209,7 @@ msgstr ""
#: config.py:458
msgid "Force Author into Author Sort?"
msgstr ""
msgstr "Tvingar författare in i författarsortering?"
#: config.py:459
msgid ""
@@ -260,11 +261,11 @@ msgstr ""
#: config.py:483
msgid "Post Processing Options"
msgstr ""
msgstr "Efterbehandlings alternativ"
#: config.py:487
msgid "Mark added/updated books when finished?"
msgstr ""
msgstr "Markera tillagda/uppdaterade böcker när det är klart?"
#: config.py:488
msgid ""
@@ -275,7 +276,7 @@ msgstr ""
#: config.py:492
msgid "Show Marked books when finished?"
msgstr ""
msgstr "Visa markerade böcker när det är klart?"
#: config.py:493
msgid ""
@@ -303,11 +304,11 @@ msgstr ""
#: config.py:507
msgid "Calculate Word Count:"
msgstr ""
msgstr "Beräkna antalet ord:"
#: config.py:520
msgid "Automatically Convert new/update books?"
msgstr ""
msgstr "Konvertera automatiskt nya/uppdaterade böcker?"
#: config.py:521
msgid ""
@@ -322,7 +323,7 @@ msgstr "GUI-alternativ"
#: config.py:529
msgid "Take URLs from Clipboard?"
msgstr ""
msgstr "Ta webbadresser från urklipp?"
#: config.py:530
msgid "Prefill URLs from valid URLs in Clipboard when Adding New."
@@ -330,7 +331,7 @@ msgstr ""
#: config.py:534
msgid "Default to Update when books selected?"
msgstr ""
msgstr "Standard att uppdatera när böcker väljs?"
#: config.py:535
msgid ""
@@ -340,7 +341,7 @@ msgstr ""
#: config.py:539
msgid "Keep 'Add New from URL(s)' dialog on top?"
msgstr ""
msgstr "Håll 'Lägg till ny från URL'-dialog överst?"
#: config.py:540
msgid ""
@@ -350,19 +351,19 @@ msgstr ""
#: config.py:544
msgid "Show estimated time left?"
msgstr ""
msgstr "Visa beräknad tid som återstår?"
#: config.py:545
msgid "When a Progress Bar is shown, show a rough estimate of the time left."
msgstr ""
msgstr "När en förloppsmätare visas, visa en grov uppskattning av den tid som återstår."
#: config.py:549
msgid "Misc Options"
msgstr ""
msgstr "Tillbehör och övriga alternativ"
#: config.py:553
msgid "Inject calibre Series when none found?"
msgstr ""
msgstr "Inför calibre serier när ingen finns?"
#: config.py:554
msgid ""
@@ -383,7 +384,7 @@ msgstr ""
#: config.py:563
msgid "Reject List"
msgstr ""
msgstr "Avvisa lista"
#: config.py:567
msgid "Edit Reject URL List"
@@ -450,7 +451,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr ""
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr ""
@@ -463,7 +464,7 @@ msgstr ""
#: config.py:673
msgid "personal.ini"
msgstr ""
msgstr "personal.ini"
#: config.py:680 config.py:784 config.py:785
msgid "Edit personal.ini"
@@ -477,7 +478,7 @@ msgstr ""
#: config.py:693
msgid "View \"Safe\" personal.ini"
msgstr ""
msgstr "Visa \"säker\" personal.ini"
#: config.py:698 config.py:775
msgid ""
@@ -487,7 +488,7 @@ msgstr ""
#: config.py:704
msgid "defaults.ini"
msgstr ""
msgstr "defaults.ini"
#: config.py:709
msgid ""
@@ -501,7 +502,7 @@ msgstr "Visa standardvärden"
#: config.py:721
msgid "Calibre Columns"
msgstr ""
msgstr "calibre-kolumner"
#: config.py:728
msgid ""
@@ -522,7 +523,7 @@ msgstr ""
#: config.py:743
msgid "Show Calibre Column Names"
msgstr ""
msgstr "Visa calibre-kolumn namn"
#: config.py:752
msgid ""
@@ -531,15 +532,15 @@ msgstr ""
#: config.py:762
msgid "Plugin Defaults"
msgstr ""
msgstr "Tilläggets standardinställningar"
#: config.py:763
msgid "Plugin Defaults (%s) (Read-Only)"
msgstr ""
msgstr "plugin standardinställningar (%s) (skrivskyddad)"
#: config.py:774
msgid "View 'Safe' personal.ini"
msgstr ""
msgstr "Visa 'säker' personal.ini"
#: config.py:808
msgid "Calibre Column Entry Names"
@@ -637,15 +638,15 @@ msgstr ""
#: config.py:937
msgid "Generate Calibre Cover:"
msgstr ""
msgstr "Generera calibre omslag:"
#: config.py:964
msgid "Plugin %(gc)s"
msgstr ""
msgstr "Tillägg %(gc)s"
#: config.py:965
msgid "Use plugin to create covers. Additional settings are below."
msgstr ""
msgstr "Använd tillägg för att skapa omslag. Ytterligare inställningar finns nedan."
#: config.py:972
msgid "Calibre Generate Cover"
@@ -660,7 +661,7 @@ msgstr ""
#: config.py:987
msgid "Generate Covers Only for New Books"
msgstr ""
msgstr "Skapa omslag endast för nya böcker"
#: config.py:988
msgid ""
@@ -681,7 +682,7 @@ msgstr ""
#: config.py:1001
msgid "%(gc)s(Plugin) Settings"
msgstr ""
msgstr "%(gc)s(Plugin) Inställningar"
#: config.py:1009
msgid ""
@@ -731,7 +732,7 @@ msgstr ""
#: config.py:1108
msgid "Which column and algorithm to use are configured in %(cp)s."
msgstr ""
msgstr "Vilken kolumn och algoritm att använda är konfigurerade i%(cp)s."
#: config.py:1118
msgid ""
@@ -803,11 +804,11 @@ msgstr "Status"
#: config.py:1239
msgid "Status:%(cmplt)s"
msgstr "Status:%(cmplt)"
msgstr "Status:%(cmplt)s"
#: config.py:1240
msgid "Status:%(inprog)s"
msgstr ""
msgstr "Status:%(inprog)s"
#: config.py:1241 config.py:1403
msgid "Series"
@@ -865,7 +866,7 @@ msgstr "Författar-ID"
msgid "Extra Tags"
msgstr "Extra taggar"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Titel"
@@ -878,7 +879,7 @@ msgstr "Berättelse-URL"
msgid "Description"
msgstr "Beskrivning"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Författare"
@@ -911,7 +912,7 @@ msgstr ""
#: config.py:1303
msgid "Update this %s column(%s) with..."
msgstr ""
msgstr "Uppdatera denna %s kolumn(%s) med..."
#: config.py:1313
msgid "Values that aren't valid for this enumeration column will be ignored."
@@ -944,7 +945,7 @@ msgstr ""
#: config.py:1335
msgid "Special column:"
msgstr ""
msgstr "Special kolumn:"
#: config.py:1340
msgid "Update/Overwrite Error Column:"
@@ -958,23 +959,23 @@ msgstr ""
#: config.py:1354
msgid "Save All Errors"
msgstr ""
msgstr "Spara alla fel"
#: config.py:1355
msgid "If unchecked, these errors will not be saved:%s"
msgstr ""
msgstr "Om inte ikryssad, kommer dessa fel inte sparas:%s"
#: config.py:1357 fff_plugin.py:1342 jobs.py:223
msgid "Not Overwriting, web site is not newer."
msgstr ""
msgstr "Skriver inte över, webbsida är inte nyare."
#: config.py:1358 fff_plugin.py:1321 jobs.py:262
msgid "Already contains %d chapters."
msgstr ""
msgstr "Innehåller redan %d kapitel."
#: config.py:1365
msgid "Saved Metadata Column:"
msgstr ""
msgstr "Sparad metadatakolumn:"
#: config.py:1366
msgid ""
@@ -1050,7 +1051,7 @@ msgstr ""
#: config.py:1460
msgid "IMAP Server Name"
msgstr ""
msgstr "IMAP-servernamn"
#: config.py:1461
msgid "Name of IMAP server--must allow IMAP4 with SSL. Eg: imap.gmail.com"
@@ -1058,7 +1059,7 @@ msgstr ""
#: config.py:1470
msgid "IMAP User Name"
msgstr ""
msgstr "IMAP-användarnamn"
#: config.py:1471
msgid ""
@@ -1068,7 +1069,7 @@ msgstr ""
#: config.py:1480
msgid "IMAP User Password"
msgstr ""
msgstr "IMAP-användarlösenord"
#: config.py:1481
msgid ""
@@ -1088,7 +1089,7 @@ msgstr ""
#: config.py:1497
msgid "IMAP Folder Name"
msgstr ""
msgstr "IMAP katalognamn"
#: config.py:1498
msgid ""
@@ -1098,7 +1099,7 @@ msgstr ""
#: config.py:1507
msgid "Mark Emails Read"
msgstr ""
msgstr "Märk lästa epostmeddelanden"
#: config.py:1508
msgid ""
@@ -1136,34 +1137,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr "Visa nedladdningsalternativ"
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr ""
msgstr "Utdata &format:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr "Välj utdataformat att skapa. Kan fastställa standard från plugin konfiguration."
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr ""
msgstr "Uppdatera calibre &metadata?"
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr ""
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr "Uppdatera EPUB omslag?"
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1212,7 +1213,7 @@ msgstr ""
#: dialogs.py:505
msgid "For Individual Books"
msgstr ""
msgstr "För individuella böcker"
#: dialogs.py:506
msgid "Get URLs and go to dialog for individual story downloads."
@@ -1228,11 +1229,11 @@ msgid ""
"Requires %s plugin."
msgstr ""
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Avbryt"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Lösenord"
@@ -1256,84 +1257,88 @@ msgstr "Användare:"
msgid "Password:"
msgstr "Lösenord:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "OK"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr ""
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr ""
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr ""
msgstr "Hämtade metadata för"
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - %s estimaterade till klara"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Hoppade över"
#: dialogs.py:692
msgid "%d day"
msgstr "%d dag"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d dagar"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d timme"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d timmar"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d minut"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d minuter"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d sekund"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d sekunder"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "mindre än 1 sekund"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr "Om FanFicFare"
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Ta bort valda böcker från listan"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr "Uppdateringsläge:"
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr ""
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr ""
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1341,103 +1346,103 @@ msgid ""
" fail."
msgstr ""
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Kommentar"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Är du säker du vill ta bort denna bok från listan?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Är du säker du vill ta bort valda %d böcker från listan?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr ""
msgstr "Anteckning"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr ""
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr ""
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr ""
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr ""
msgstr "Lista över böcker att avvisa"
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr ""
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr "Ta bort valda URL:er från listan"
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr ""
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr ""
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr ""
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr "Sök efter sträng i redigeringsrutan."
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr "Hitta:"
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr "Hitta"
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr "Teckenlägeskänslig"
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr ""
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr ""
msgstr "Gå tillbaka för att åtgärda fel?"
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr ""
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr "Klicka för att gå till rad %s"
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr "Återgå till redigering"
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr "Spara i alla fall"
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr "Ange lösenord för e-postadress till %s:"
@@ -1451,15 +1456,15 @@ msgstr ""
#: fff_plugin.py:293
msgid "&Download from URLs"
msgstr ""
msgstr "&Hämta från adresser"
#: fff_plugin.py:295
msgid "Download FanFiction Books from URLs"
msgstr ""
msgstr "Hämta FanFiction Böcker från adresser"
#: fff_plugin.py:298
msgid "&Update Existing FanFiction Books"
msgstr ""
msgstr "&Uppdatera existerande FanFiction böcker"
#: fff_plugin.py:303
msgid "Get Story URLs from &Email"
@@ -1507,7 +1512,7 @@ msgstr ""
#: fff_plugin.py:338
msgid "Add to \"Send to Device\" Lists"
msgstr ""
msgstr "Lägg till \"Skicka till enhet\"-listor"
#: fff_plugin.py:340
msgid "Mark Unread: Add to \"To Read\" Lists"
@@ -1523,15 +1528,15 @@ msgstr ""
#: fff_plugin.py:367
msgid "Reject Selected Books"
msgstr ""
msgstr "Avvisa markerade böcker"
#: fff_plugin.py:375
msgid "&Configure FanFicFare"
msgstr ""
msgstr "&Anpassa FanFicFare"
#: fff_plugin.py:378
msgid "Configure FanFicFare"
msgstr ""
msgstr "Anpassa FanFicFare"
#: fff_plugin.py:433
msgid "Cannot Update Reading Lists from Device View"
@@ -1808,16 +1813,12 @@ msgstr ""
#: fff_plugin.py:1159
msgid "Click '<b>Yes</b>' to Skip."
msgstr ""
msgstr "Tryck '<b>Ja</b>' för att hoppa över."
#: fff_plugin.py:1162
msgid "Story in Series Anthology(%s)."
msgstr ""
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Hoppade över"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Lägg till"
@@ -1852,15 +1853,15 @@ msgstr ""
#: fff_plugin.py:1274
msgid "In library: <a href=\"%(liburl)s\">%(liburl)s</a>"
msgstr ""
msgstr "I bibliotek: <a href=\"%(liburl)s\">%(liburl)s</a>"
#: fff_plugin.py:1275 fff_plugin.py:1289
msgid "New URL: <a href=\"%(newurl)s\">%(newurl)s</a>"
msgstr ""
msgstr "Ny webbadress: <a href=\"%(newurl)s\">%(newurl)s</a>"
#: fff_plugin.py:1276
msgid "Click '<b>Yes</b>' to update/overwrite book with new URL."
msgstr ""
msgstr "Klicka '<b>Ja</b>' för att uppdatera/skriva över bok med ny webbadress."
#: fff_plugin.py:1277
msgid "Click '<b>No</b>' to skip updating/overwriting this book."
@@ -1884,11 +1885,11 @@ msgstr ""
#: fff_plugin.py:1290
msgid "Click '<b>Yes</b>' to a new book with new URL."
msgstr ""
msgstr "Klicka '<b>Ja</b>' till en ny bok med ny webbadress."
#: fff_plugin.py:1291
msgid "Click '<b>No</b>' to skip URL."
msgstr ""
msgstr "Klicka '<b>Nej</b>' för att hoppa över URL."
#: fff_plugin.py:1297
msgid "Update declined by user due to differing story URL(%s)"
@@ -1920,7 +1921,7 @@ msgstr ""
#: fff_plugin.py:1461 fff_plugin.py:1659 fff_plugin.py:1689
msgid "See log for details."
msgstr ""
msgstr "Se logg för detaljer."
#: fff_plugin.py:1462
msgid "Proceed with updating your library(Error Column, if configured)?"
@@ -1936,11 +1937,11 @@ msgstr ""
#: fff_plugin.py:1477 fff_plugin.py:1714
msgid "FanFicFare log"
msgstr ""
msgstr "FanFicFare logg"
#: fff_plugin.py:1497
msgid "Download %s FanFiction Book(s)"
msgstr ""
msgstr "Hämta %s FanFiction bok/böcker"
#: fff_plugin.py:1504
msgid "Starting %d FanFicFare Downloads"
@@ -1952,7 +1953,7 @@ msgstr "Berättelsedetaljer:"
#: fff_plugin.py:1538
msgid "Error Updating Metadata"
msgstr ""
msgstr "Fel vid uppdatering av metadata"
#: fff_plugin.py:1539
msgid ""
File diff suppressed because it is too large Load Diff
+80 -79
View File
@@ -2,14 +2,15 @@
# Copyright (C) YEAR ORGANIZATION
#
# Translators:
# Andrii <dexteritymaster@gmail.com>, 2016
# Andrii <dexteritymaster@gmail.com>, 2014-2015
# Yuri Chornoivan <yurchor@ukr.net>, 2014
msgid ""
msgstr ""
"Project-Id-Version: calibre-plugins\n"
"POT-Creation-Date: 2016-03-22 21:58+Central Daylight Time\n"
"PO-Revision-Date: 2016-03-29 09:06+0000\n"
"Last-Translator: Kovid Goyal <kovid@kovidgoyal.net>\n"
"POT-Creation-Date: 2016-04-28 16:06+Central Daylight Time\n"
"PO-Revision-Date: 2016-06-26 20:50+0000\n"
"Last-Translator: Andrii <dexteritymaster@gmail.com>\n"
"Language-Team: Ukrainian (http://www.transifex.com/calibre/calibre-plugins/language/uk/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -18,11 +19,11 @@ msgstr ""
"Language: uk\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: __init__.py:42
#: __init__.py:48
msgid "UI plugin to download FanFiction stories from various sites."
msgstr "Додаток для отримання творчості фанів з різноманітних сайтів."
#: __init__.py:116
#: __init__.py:122
msgid ""
"Path to the calibre library. Default is to use the path stored in the "
"settings."
@@ -30,55 +31,55 @@ msgstr "Шлях до бібліотеки calibre. Типово буде вик
#: common_utils.py:398
msgid "Keyboard shortcuts"
msgstr ""
msgstr "Клавіатурні гарячі клавіші"
#: common_utils.py:444
msgid "Undefined"
msgstr ""
msgstr "Невизначено"
#: common_utils.py:464
msgid "Prefs Viewer dialog"
msgstr ""
msgstr "Налаштування Вікна Перегляду"
#: common_utils.py:465
msgid "Preferences for: "
msgstr ""
msgstr "Налаштування для:"
#: common_utils.py:497
msgid "Clear"
msgstr ""
msgstr "Очистити"
#: common_utils.py:499
msgid "Clear all settings for this plugin"
msgstr ""
msgstr "Очистити всі налаштування для цього плагіну"
#: common_utils.py:526
msgid ""
"Are you sure you want to clear your settings in this library for this "
"plugin?"
msgstr ""
msgstr "Ви впевнені, що бажаєте очистити ваші налаштування в цій бібліотцеці для даного плагіну?"
#: common_utils.py:527
msgid ""
"Any settings in other libraries or stored in a JSON file in your calibre "
"plugins folder will not be touched."
msgstr ""
msgstr "Будь-які налаштування в інших бібліотеках, або збережені в файлі JSON в папці ваший плагінів не будуть змінені."
#: common_utils.py:528
msgid "You must restart calibre afterwards."
msgstr ""
msgstr "Після цього ви повинні перезавантажити Calibre."
#: common_utils.py:537
msgid "All settings for this plugin in this library have been cleared."
msgstr ""
msgstr "Всі налаштування для цього плагіну в цій бібліотеці були очищені."
#: common_utils.py:538
msgid "Please restart calibre now."
msgstr ""
msgstr "Будь-ласка перезавантажте Calibre."
#: common_utils.py:540
msgid "Restart calibre now"
msgstr ""
msgstr "Перезавантажити Calibre"
#: config.py:192
msgid "List of Supported Sites"
@@ -94,7 +95,7 @@ msgstr "Основні"
#: config.py:220
msgid "Calibre Cover"
msgstr ""
msgstr "Обкладинка Calibre"
#: config.py:228
msgid "Standard Columns"
@@ -106,7 +107,7 @@ msgstr "Нетипові стовпчики"
#: config.py:234
msgid "Email Settings"
msgstr ""
msgstr "Налаштування Пошти"
#: config.py:237
msgid "Other"
@@ -449,7 +450,7 @@ msgid ""
"<b>http://...,title by author - note</b>"
msgstr ""
#: config.py:650 dialogs.py:1102
#: config.py:650 dialogs.py:1119
msgid "Add this reason to all URLs added:"
msgstr ""
@@ -864,7 +865,7 @@ msgstr "Ідентифікатор автора"
msgid "Extra Tags"
msgstr "Додаткові мітки"
#: config.py:1255 config.py:1395 dialogs.py:893 dialogs.py:989
#: config.py:1255 config.py:1395 dialogs.py:910 dialogs.py:1006
#: fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Title"
msgstr "Назва"
@@ -877,7 +878,7 @@ msgstr "Адреса твору"
msgid "Description"
msgstr "Опис"
#: config.py:1258 dialogs.py:893 dialogs.py:989 fff_plugin.py:1464
#: config.py:1258 dialogs.py:910 dialogs.py:1006 fff_plugin.py:1464
#: fff_plugin.py:1663 fff_plugin.py:1693
msgid "Author"
msgstr "Автор"
@@ -1135,34 +1136,34 @@ msgid ""
"<br>Use this feature at your own risk. </b>"
msgstr ""
#: dialogs.py:245 dialogs.py:773
#: dialogs.py:245 dialogs.py:790
msgid "Show Download Options"
msgstr ""
#: dialogs.py:264 dialogs.py:793
#: dialogs.py:264 dialogs.py:810
msgid "Output &Format:"
msgstr "&Формат виводу:"
#: dialogs.py:272 dialogs.py:801
#: dialogs.py:272 dialogs.py:818
msgid ""
"Choose output format to create. May set default from plugin configuration."
msgstr ""
#: dialogs.py:300 dialogs.py:821
#: dialogs.py:300 dialogs.py:838
msgid "Update Calibre &Metadata?"
msgstr ""
#: dialogs.py:301 dialogs.py:822
#: dialogs.py:301 dialogs.py:839
msgid ""
"Update metadata for existing stories in Calibre from web site?\n"
"(Columns set to 'New Only' in the column tabs will only be set for new books.)"
msgstr ""
#: dialogs.py:307 dialogs.py:826
#: dialogs.py:307 dialogs.py:843
msgid "Update EPUB Cover?"
msgstr ""
#: dialogs.py:308 dialogs.py:827
#: dialogs.py:308 dialogs.py:844
msgid ""
"Update book cover image from site or defaults (if found) <i>inside</i> the "
"EPUB when EPUB is updated."
@@ -1227,11 +1228,11 @@ msgid ""
"Requires %s plugin."
msgstr ""
#: dialogs.py:516 dialogs.py:570 dialogs.py:597 dialogs.py:1504
#: dialogs.py:516 dialogs.py:570 dialogs.py:617 dialogs.py:1521
msgid "Cancel"
msgstr "Скасувати"
#: dialogs.py:548 dialogs.py:1492
#: dialogs.py:548 dialogs.py:1509
msgid "Password"
msgstr "Пароль"
@@ -1255,84 +1256,88 @@ msgstr "Користувач:"
msgid "Password:"
msgstr "Пароль:"
#: dialogs.py:566 dialogs.py:715 dialogs.py:1500
#: dialogs.py:566 dialogs.py:732 dialogs.py:1517
msgid "OK"
msgstr "Гаразд"
#: dialogs.py:592 fff_plugin.py:958
#: dialogs.py:588 dialogs.py:612 fff_plugin.py:958
msgid "Fetching metadata for stories..."
msgstr ""
#: dialogs.py:593 fff_plugin.py:959
#: dialogs.py:589 dialogs.py:613 fff_plugin.py:959
msgid "Downloading metadata for stories"
msgstr ""
#: dialogs.py:594 fff_plugin.py:960
#: dialogs.py:590 dialogs.py:614 fff_plugin.py:960
msgid "Fetched metadata for"
msgstr ""
#: dialogs.py:624
#: dialogs.py:643
msgid " - %s estimated until done"
msgstr " - приблизно %s до завершення"
#: dialogs.py:675
#: dialogs.py:661 fff_plugin.py:1168
msgid "Skipped"
msgstr "Пропущено"
#: dialogs.py:692
msgid "%d day"
msgstr "%d день"
#: dialogs.py:675
#: dialogs.py:692
msgid "%d days"
msgstr "%d дні"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hour"
msgstr "%d година"
#: dialogs.py:676
#: dialogs.py:693
msgid "%d hours"
msgstr "%d години"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minute"
msgstr "%d хвилина"
#: dialogs.py:677
#: dialogs.py:694
msgid "%d minutes"
msgstr "%d хвилин"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d second"
msgstr "%d секунда"
#: dialogs.py:678
#: dialogs.py:695
msgid "%d seconds"
msgstr "%d секунд"
#: dialogs.py:693
#: dialogs.py:710
msgid "less than 1 second"
msgstr "менше за секунду"
#: dialogs.py:710 fff_plugin.py:381 fff_plugin.py:384
#: dialogs.py:727 fff_plugin.py:381 fff_plugin.py:384
msgid "About FanFicFare"
msgstr ""
#: dialogs.py:764
#: dialogs.py:781
msgid "Remove selected books from the list"
msgstr "Вилучити позначені книги зі списку"
#: dialogs.py:806
#: dialogs.py:823
msgid "Update Mode:"
msgstr ""
#: dialogs.py:809
#: dialogs.py:826
msgid ""
"What sort of update to perform. May set default from plugin configuration."
msgstr ""
#: dialogs.py:831
#: dialogs.py:848
msgid "Background Metadata?"
msgstr ""
#: dialogs.py:832
#: dialogs.py:849
msgid ""
"Collect Metadata from sites in a Background process.<br />This returns "
"control to you quicker while updating, but you won't be asked for "
@@ -1340,103 +1345,103 @@ msgid ""
" fail."
msgstr ""
#: dialogs.py:893 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
#: dialogs.py:910 fff_plugin.py:1464 fff_plugin.py:1663 fff_plugin.py:1693
msgid "Comment"
msgstr "Коментар"
#: dialogs.py:961
#: dialogs.py:978
msgid "Are you sure you want to remove this book from the list?"
msgstr "Ви справді хочете вилучити цю книгу зі списку?"
#: dialogs.py:963
#: dialogs.py:980
msgid "Are you sure you want to remove the selected %d books from the list?"
msgstr "Ви справді хочете вилучити позначені %d книг зі списку?"
#: dialogs.py:989
#: dialogs.py:1006
msgid "Note"
msgstr "Примітка"
#: dialogs.py:1028
#: dialogs.py:1045
msgid "Select or Edit Reject Note."
msgstr ""
#: dialogs.py:1037
#: dialogs.py:1054
msgid "Are you sure you want to remove this URL from the list?"
msgstr ""
#: dialogs.py:1039
#: dialogs.py:1056
msgid "Are you sure you want to remove the %d selected URLs from the list?"
msgstr ""
#: dialogs.py:1057
#: dialogs.py:1074
msgid "List of Books to Reject"
msgstr ""
#: dialogs.py:1070
#: dialogs.py:1087
msgid ""
"FFF will remember these URLs and display the note and offer to reject them "
"if you try to download them again later."
msgstr ""
#: dialogs.py:1084
#: dialogs.py:1101
msgid "Remove selected URLs from the list"
msgstr ""
#: dialogs.py:1099 dialogs.py:1103
#: dialogs.py:1116 dialogs.py:1120
msgid "This will be added to whatever note you've set for each URL above."
msgstr ""
#: dialogs.py:1113
#: dialogs.py:1130
msgid "Delete Books (including books without FanFiction URLs)?"
msgstr ""
#: dialogs.py:1114
#: dialogs.py:1131
msgid "Delete the selected books after adding them to the Rejected URLs list."
msgstr ""
#: dialogs.py:1270
#: dialogs.py:1287
msgid "Search for string in edit box."
msgstr ""
#: dialogs.py:1273
#: dialogs.py:1290
msgid "Find:"
msgstr ""
#: dialogs.py:1278
#: dialogs.py:1295
msgid "Find"
msgstr ""
#: dialogs.py:1288
#: dialogs.py:1305
msgid "Case sensitive"
msgstr ""
#: dialogs.py:1289
#: dialogs.py:1306
msgid ""
"Search for case sensitive string; don't treat Harry, HARRY and harry all the"
" same."
msgstr ""
#: dialogs.py:1318
#: dialogs.py:1335
msgid "Go back to fix errors?"
msgstr ""
#: dialogs.py:1430
#: dialogs.py:1447
msgid "Click an error below to return to Editing directly on that line:"
msgstr ""
#: dialogs.py:1444
#: dialogs.py:1461
msgid "Click to go to line %s"
msgstr ""
#: dialogs.py:1460
#: dialogs.py:1477
msgid "Return to Editing"
msgstr ""
#: dialogs.py:1464
#: dialogs.py:1481
msgid "Save Anyway"
msgstr ""
#: dialogs.py:1493
#: dialogs.py:1510
msgid "Enter Email Password for %s:"
msgstr ""
@@ -1813,10 +1818,6 @@ msgstr ""
msgid "Story in Series Anthology(%s)."
msgstr ""
#: fff_plugin.py:1168
msgid "Skipped"
msgstr "Пропущено"
#: fff_plugin.py:1178
msgid "Add"
msgstr "Додати"
+1 -10
View File
@@ -85,7 +85,6 @@ import adapter_hpfanficarchivecom
import adapter_twilightarchivescom
import adapter_nhamagicalworldsus
import adapter_hlfictionnet
import adapter_grangerenchantedcom
import adapter_dracoandginnycom
import adapter_scarvesandcoffeenet
import adapter_thepetulantpoetesscom
@@ -102,13 +101,9 @@ import adapter_efictionestelielde
import adapter_pommedesangcom
import adapter_restrictedsectionorg
import adapter_imagineeficcom
import adapter_buffynfaithnet
import adapter_psychficcom
import adapter_tokrafandomnetcom
import adapter_asr3slashzoneorg
import adapter_nickandgregnet
import adapter_potterheadsanonymouscom
import adapter_scarheadnet
import adapter_fictionpadcom
import adapter_storiesonlinenet
import adapter_trekiverseorg
@@ -120,7 +115,6 @@ import adapter_nocturnallightnet
import adapter_fanfichu
import adapter_fanfictioncsodaidokhu
import adapter_fictionmaniatv
import adapter_bdsmgeschichten
import adapter_tolkienfanfiction
import adapter_themaplebookshelf
import adapter_fannation
@@ -139,13 +133,10 @@ import adapter_ninelivesarchivecom
import adapter_masseffect2in
import adapter_quotevcom
import adapter_mcstoriescom
import adapter_lucifaelff
import adapter_buffygilescom
#import adapter_rubyquillcom
import adapter_andromedawebcom # Not all lables are captured
import adapter_andromedawebcom
import adapter_artemisfowlcom
import adapter_rabidreadercom
import adapter_naiceanilmenet
## This bit of complexity allows adapters to be added by just adding
@@ -77,7 +77,8 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
## Login seems to be reasonably standard across eFiction sites.
def needToLoginCheck(self, data):
if 'This story contains adult content and/or themes.' in data \
or "That password doesn't match the one in our database" in data:
or "That password doesn't match the one in our database" in data \
or "Member Login" in data:
return True
else:
return False
@@ -1,347 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2014 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.
#
import time
import logging
logger = logging.getLogger(__name__)
import re
import urllib2
import urlparse
import time
from bs4.element import Tag, Comment
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
def _translate_date_german_english(date):
fullmon = {"Januar":"01",
"Februar":"02",
u"März":"03",
"April":"04",
"Mai":"05",
"Juni":"06",
"Juli":"07",
"August":"08",
"September":"09",
"Oktober":"10",
"November":"11",
"Dezember":"12"}
for (name,num) in fullmon.items():
date = date.replace(name,num)
return date
_REGEX_TRAILING_DIGIT = re.compile("(\d+)$")
_REGEX_DASH_TO_END = re.compile("-[^-]+$")
_REGEX_CHAPTER_TITLE = re.compile(ur"""
\s*
[\u2013-]?
\s*
([\dIVX-]+)?
\.?
\s*
[\[\(]?
\s*
(Teil|Kapitel|Tag)?
\s*
([\dIVX-]+)?
\s*
[\]\)]?
\s*
$
""", re.VERBOSE)
_INITIAL_STEP = 5
class BdsmGeschichtenAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.decode = ["utf8", "Windows-1252"]
self.story.setMetadata('siteabbrev','bdsmgesch')
# Replace possible chapter numbering
chapterMatch = _REGEX_TRAILING_DIGIT.search(url)
if chapterMatch is None:
self.maxChapter = 1
else:
self.maxChapter = int(chapterMatch.group(1))
# url = re.sub(_REGEX_TRAILING_DIGIT, "1", url)
# set storyId
self.story.setMetadata('storyId', re.compile(self.getSiteURLPattern()).match(url).group('storyId'))
# normalize URL
self._setURL('http://%s/%s' % (self.getSiteDomain(), self.story.getMetadata('storyId')))
self.dateformat = '%d. %m %Y - %H:%M'
@staticmethod
def getSiteDomain():
return 'bdsm-geschichten.net'
@classmethod
def getAcceptDomains(cls):
return ['www.bdsm-geschichten.net', 'www.bdsm-geschichten.net']
@classmethod
def getSiteExampleURLs(cls):
return "http://www.bdsm-geschichten.net/title-of-story-1 http://bdsm-geschichten.net/title-of-story-1"
def getSiteURLPattern(self):
return r"http://(www\.)?bdsm-geschichten.net/(?P<storyId>[a-zA-Z0-9_-]+)"
def extractChapterUrlsAndMetadata(self):
if not (self.is_adult or self.getConfig("is_adult")):
raise exceptions.AdultCheckRequired(self.url)
try:
data1 = self._fetchUrl(self.url)
soup = self.make_soup(data1)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
#strip comments from soup
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
# Cache the soups so we won't have to redownload in getChapterText later
self.soupsCache = {}
self.soupsCache[self.url] = soup
# author
authorDiv = soup.find("div", "author-pane-line author-name")
authorId = authorDiv.string.strip()
self.story.setMetadata('authorId', authorId)
self.story.setMetadata('author', authorId)
# TODO not really true need to be loggedin for this to work or fetch userid
self.story.setMetadata('authorUrl','http://'+self.host+'/'+authorId)
# TODO better metadata
date = soup.find("div", {"class": "submitted"}).string.strip()
# 11. April 2015 - 17:08
date = re.sub(r"(\d+\. \D+ \d+ - \d+:\d+).*", r"\1", date)
date = _translate_date_german_english(date)
self.story.setMetadata('datePublished', makeDate(date, self.dateformat))
title1 = soup.find("h1", {'class': 'title'}).string
for tagLink in soup.find("ul", "taxonomy").findAll("a"):
self.story.addToList('category', tagLink.string)
## Retrieve chapter soups
if self.getConfig('find_chapters') == 'guess':
self.chapterUrls = []
self._find_chapters_by_guessing(title1)
else:
self._find_chapters_by_parsing(soup)
firstChapterUrl = self.chapterUrls[0][1]
if firstChapterUrl in self.soupsCache:
firstChapterSoup = self.soupsCache[firstChapterUrl]
h1 = firstChapterSoup.find("h1").text
else:
h1 = soup.find("h1").text
h1 = re.sub(_REGEX_CHAPTER_TITLE, "", h1)
self.story.setMetadata('title', h1)
self.story.setMetadata('numChapters', len(self.chapterUrls))
return
def _find_chapters_by_parsing(self, soup):
# store original soup
origSoup = soup
#
# find first chapter
#
firstLink = None
firstLinkDiv = soup.find("div", "field-field-erster-teil")
if firstLinkDiv is not None:
firstLink = "http://%s%s" % (self.getSiteDomain(), firstLinkDiv.findNext("a")['href'])
logger.debug("Found first chapter right away <%s>" % firstLink)
try:
soup = self.make_soup(self._fetchUrl(firstLink))
self.soupsCache[firstLink] = soup
self.chapterUrls.insert(0, (soup.find("h1").text, firstLink))
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise exceptions.StoryDoesNotExist(firstLink)
else:
logger.debug("DIDN'T find first chapter right away")
# parse previous Link until first
while True:
prevLink = None
prevLinkDiv = soup.find("div", "field-field-vorheriger-teil")
if prevLinkDiv is not None:
prevLink = prevLinkDiv.find("a")
if prevLink is None:
prevLink = soup.find("a", text=re.compile("&lt;&lt;&lt;")) # <<<
if prevLink is None:
logger.debug("Couldn't find prev part")
break
else:
logger.debug("Previous Chapter <%s>" % prevLink)
if type(prevLink) != Tag or prevLink.name != "a":
prevLink = prevLink.findParent("a")
if prevLink is None or '#' in prevLink['href']:
logger.debug("Couldn't find prev part (false positive) <%s>" % prevLink)
break
prevLink = prevLink['href']
try:
soup = self.make_soup(self._fetchUrl(prevLink))
self.soupsCache[prevLink] = soup
prevTtitle = soup.find("h1", {'class': 'title'}).string
self.chapterUrls.insert(0, (prevTtitle, prevLink))
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(nextLink)
else:
raise e
firstLink = prevLink
# if first chapter couldn't be determined, assume the URL originally
# passed is the first chapter
if firstLink is None:
logger.debug("Couldn't set first chapter")
firstLink = self.url
self.chapterUrls.insert(0, (soup.find("h1").text, firstLink))
# set first URL
logger.debug("Set first link: %s" % firstLink)
self._setURL(firstLink)
self.story.setMetadata('storyId', re.compile(self.getSiteURLPattern()).match(firstLink).group('storyId'))
#
# Parse next chapters
#
while True:
nextLink = None
nextLinkDiv = soup.find("div", "field-field-naechster-teil")
if nextLinkDiv is not None:
nextLink = nextLinkDiv.find("a")
if nextLink is None:
nextLink = soup.find("a", text=re.compile("&gt;&gt;&gt;"))
if nextLink is None:
nextLink = soup.find("a", text=re.compile("Fortsetzung"))
if nextLink is None:
logger.debug("Couldn't find next part")
break
else:
if type(nextLink) != Tag or nextLink.name != "a":
nextLink = nextLink.findParent("a")
if nextLink is None or '#' in nextLink['href']:
logger.debug("Couldn't find next part (false positive) <%s>" % nextLink)
break
nextLink = nextLink['href']
if not nextLink.startswith('http:'):
nextLink = 'http://' + self.getSiteDomain() + nextLink
for loadedChapter in self.chapterUrls:
if loadedChapter[0] == nextLink:
logger.debug("ERROR: Repeating chapter <%s> Try to fix it" % nextLink)
nextLinkMatch = _REGEX_TRAILING_DIGIT.match(nextLink)
if nextLinkMatch is not None:
curChap = nextLinkMatch.group(1)
nextLink = re.sub(_REGEX_TRAILING_DIGIT, unicode(int(curChap) + 1), nextLink)
else:
break
try:
data = self._fetchUrl(nextLink)
soup = self.make_soup(data)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(nextLink)
else:
raise e
title2 = soup.find("h1", {'class': 'title'}).string
self.chapterUrls.append((title2, nextLink))
logger.debug("Grabbing next chapter URL " + nextLink)
self.soupsCache[nextLink] = soup
# [comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
logger.debug("Chapters: %s" % self.chapterUrls)
def _find_chapters_by_guessing(self, title1):
step = _INITIAL_STEP
curMax = self.maxChapter + step
lastHit = True
while True:
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, unicode(curMax), self.url)
if nextChapterUrl == self.url:
logger.debug("Unable to guess next chapter because URL doesn't end in numbers")
break;
try:
logger.debug("Trying chapter URL " + nextChapterUrl)
data = self._fetchUrl(nextChapterUrl)
hit = True
except urllib2.HTTPError, e:
if e.code == 404:
hit = False
else:
raise e
if hit:
logger.debug("Found chapter URL " + nextChapterUrl)
self.maxChapter = curMax
self.soupsCache[nextChapterUrl] = self.make_soup(data)
if not lastHit:
break
lastHit = curMax
curMax += step
else:
lastHit = False
curMax -= 1
logger.debug(curMax)
for i in xrange(1, self.maxChapter):
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, unicode(i), self.url)
nextChapterTitle = re.sub("1", unicode(i), title1)
self.chapterUrls.append((nextChapterTitle, nextChapterUrl))
def getChapterText(self, url):
if url in self.soupsCache:
logger.debug('Getting chapter <%s> from cache' % url)
soup = self.soupsCache[url]
else:
logger.debug('Downloading chapter <%s>' % url)
data1 = self._fetchUrl(url)
soup = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
# get story text
storyDiv1 = soup.new_tag("div")
for para in soup.find("div", "full-node").find('div', 'content').findAll("p"):
storyDiv1.append(para)
storyDiv1.append(soup.new_tag("br"))
storytext = self.utf8FromSoup(url,storyDiv1)
return storytext
def getClass():
return BdsmGeschichtenAdapter
@@ -28,7 +28,7 @@ class BloodshedverseComAdapter(BaseSiteAdapter):
READ_URL_TEMPLATE = BASE_URL + 'stories.php?go=read&no=%s'
STARTED_DATETIME_FORMAT = '%m/%d/%Y'
UPDATED_DATETIME_FORMAT = '%m/%d/%Y %I:%M'
UPDATED_DATETIME_FORMAT = '%m/%d/%Y %I:%M %p'
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
@@ -168,14 +168,8 @@ class BloodshedverseComAdapter(BaseSiteAdapter):
self.story.setMetadata('datePublished', makeDate(value, self.STARTED_DATETIME_FORMAT))
elif key == 'Updated':
date_string, period = value.rsplit(' ', 1)
date = makeDate(date_string, self.UPDATED_DATETIME_FORMAT)
# Rather ugly hack to work around Calibre's changing of
# Python's locale setting, causing am/pm to not be properly
# parsed by strptime() when using a non-english locale
if period == 'pm':
date += timedelta(hours=12)
date = makeDate(value, self.UPDATED_DATETIME_FORMAT)
# ugly %p(am/pm) hack moved into makeDate so other sites can use it.
self.story.setMetadata('dateUpdated', date)
if self.story.getMetadata('rating') == 'NC-17' and not (self.is_adult or self.getConfig('is_adult')):
@@ -1,292 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2013 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.
#
import time
import logging
logger = logging.getLogger(__name__)
import re
import urllib2
import cookielib as cl
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
# This function is called by the downloader in all adapter_*.py files
# in this dir to register the adapter class. So it needs to be
# updated to reflect the class below it. That, plus getSiteDomain()
# take care of 'Registering'.
def getClass():
return BuffyNFaithNetAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class BuffyNFaithNetAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.setHeader()
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 correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL. gets rid of chapter if there, left with ch 1 URL on this site
nurl = "http://"+self.getSiteDomain()+"/fanfictions/index.php?act=vie&id="+self.story.getMetadata('storyId')
self._setURL(nurl)
else:
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','bnfnet')
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'buffynfaith.net'
@classmethod
def stripURLParameters(cls,url):
"Only needs to be overriden if URL contains more than one parameter"
## This adapter needs at least two parameters left on the URL, act and id
return re.sub(r"(\?act=(vie|ovr)&id=\d+)&.*$",r"\1",url)
def setHeader(self):
"buffynfaith.net wants a Referer for images. Used both above and below(after cookieproc added)"
self.opener.addheaders.append(('Referer', 'http://'+self.getSiteDomain()+'/'))
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=vie&id=1234 http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=ovr&id=1234 http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=vie&id=1234&ch=2"
def getSiteURLPattern(self):
#http://buffynfaith.net/fanfictions/index.php?act=vie&id=963
#http://buffynfaith.net/fanfictions/index.php?act=vie&id=949
#http://buffynfaith.net/fanfictions/index.php?act=vie&id=949&ch=2
p = re.escape("http://"+self.getSiteDomain()+"/fanfictions/index.php?act=")+\
r"(vie|ovr)&id=(?P<id>\d+)(&ch=(?P<ch>\d+))?$"
return p
def use_pagecache(self):
'''
adapters that will work with the page cache need to implement
this and change it to True.
'''
return True
def extractChapterUrlsAndMetadata(self):
dateformat = "%d %B %Y"
url = self.url
logger.debug("URL: "+url)
#set a cookie to get past adult check
if self.is_adult or self.getConfig("is_adult"):
cookie = cl.Cookie(version=0, name='my_age', value='yes',
port=None, port_specified=False,
domain=self.getSiteDomain(), domain_specified=False, domain_initial_dot=False,
path='/', path_specified=True,
secure=False,
expires=time.time()+10000,
discard=False,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False)
self.get_cookiejar().set_cookie(cookie)
self.setHeader()
try:
data = self._fetchUrl(url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
#print data
if "ADULT CONTENT WARNING" in data:
raise exceptions.AdultCheckRequired(self.url)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
# Now go hunting for all the meta data and the chapter list.
#stuff in <head>: description
svalue = soup.head.find('meta',attrs={'name':'description'})['content']
#self.story.setMetadata('description',svalue)
self.setDescription(url,svalue)
#useful stuff in rest of doc, all contained in this:
doc = soup.body.find('div', id='my_wrapper')
#first the site category (more of a genre to me, meh) and title, in this element:
mt = doc.find('div',attrs={'class':'maintitle'})
self.story.addToList('genre',mt.findAll('a')[1].string)
self.story.setMetadata('title',stripHTML(mt).split(u'»')[-1].strip())
del mt
#the actual category, for me, is 'Buffy: The Vampire Slayer'
#self.story.addToList('category','Buffy: The Vampire Slayer')
#No need to do it here, it is better to set it in in plugin-defaults.ini and defaults.ini
#then a block that sits in a table cell like so:
#(contains a lot of metadata)
mblock = doc.find('td', align='left', width = '70%').contents
while len(mblock) > 0:
i = mblock.pop(0)
if 'Author:' in i.string:
#drop empty space
mblock.pop(0)
#get author link
a = mblock.pop(0)
authre = re.escape('./index.php?act=bio&id=')+'(?P<authid>\d+)'
m = re.match(authre,a['href'])
self.story.setMetadata('author',a.string)
self.story.setMetadata('authorId',m.group('authid'))
authurl = u'http://%s/fanfictions/index.php?act=bio&id=%s' % ( self.getSiteDomain(),
self.story.getMetadata('authorId'))
self.story.setMetadata('authorUrl',authurl,condremoveentities=False)
#drop empty space
mblock.pop(0)
if 'Rating:' in i.string:
self.story.setMetadata('rating',mblock.pop(0).strip())
if 'Published:' in i.string:
date = mblock.pop(0).strip()
#get rid of 'st', 'nd', 'rd', 'th' after day number
date = date[0:2]+date[4:]
self.story.setMetadata('datePublished',makeDate(date, dateformat))
if 'Last Updated:' in i.string:
date = mblock.pop(0).strip()
#get rid of 'st', 'nd', 'rd', 'th' after day number
date = date[0:2]+date[4:]
self.story.setMetadata('dateUpdated',makeDate(date, dateformat))
if 'Genre:' in i.string:
genres = mblock.pop(0).strip()
genres = genres.split('/')
for genre in genres: self.story.addToList('genre',genre)
#end ifs
#end while
# Find the chapter selector
select = soup.find('select', { 'name' : 'ch' } )
if select is None:
# no selector found, so it's a one-chapter story.
#self.chapterUrls.append((self.story.getMetadata('title'),url))
self.chapterUrls.append((self.story.getMetadata('title'),url))
else:
allOptions = select.findAll('option')
for o in allOptions:
url = u'http://%s/fanfictions/index.php?act=vie&id=%s&ch=%s' % ( self.getSiteDomain(),
self.story.getMetadata('storyId'),
o['value'])
title = u"%s" % o
title = stripHTML(title)
ts = title.split(' ',1)
title = ts[0]+'. '+ts[1]
self.chapterUrls.append((title,url))
self.story.setMetadata('numChapters',len(self.chapterUrls))
## Go scrape the rest of the metadata from the author's page.
data = self._fetchUrl(self.story.getMetadata('authorUrl'))
soup = self.make_soup(data)
#find the story link and its parent div
storya = soup.find('a',{'href':self.story.getMetadata('storyUrl')})
storydiv = storya.parent
#warnings come under a <spawn> tag. Never seen that before...
#appears to just be a line of freeform text, not necessarily a list
#optional
spawn = storydiv.find('spawn',{'id':'warnings'})
if spawn is not None:
warns = spawn.nextSibling.strip()
self.story.addToList('warnings',warns)
#some meta in spans - this should get all, even the ones jammed in a table
spans = storydiv.findAll('span')
for s in spans:
if s.string == 'Ship:':
list = s.nextSibling.strip().split()
self.story.extendList('ships',list)
if s.string == 'Characters:':
list = s.nextSibling.strip().split(',')
self.story.extendList('characters',list)
if s.string == 'Status:':
st = s.nextSibling.strip()
self.story.setMetadata('status',st)
if s.string == 'Words:':
st = s.nextSibling.strip()
self.story.setMetadata('numWords',st)
#reviews - is this worth having?
#ffnet adapter gathers it, don't know if anything else does
#or if it's ever going to be used!
a = storydiv.find('a',{'id':'bold-blue'})
if a:
revs = a.nextSibling.strip()[1:-1]
self.story.setMetadata('reviews',st)
else:
revs = '0'
self.story.setMetadata('reviews',st)
# 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' : 'fanfiction'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
#remove all the unnecessary bookmark tags
[s.extract() for s in div('div',{'class':"tiny_box2"})]
#is there a review link?
r = div.find('a',href=re.compile(re.escape("./index.php?act=irv")+".*$"))
if r is not None:
#remove the review link and its parent div
r.parent.extract()
#There might also be a link to the sequel on the last chapter
#I'm inclined to keep it in, but the URL needs to be changed from relative to absolute
#Shame there isn't proper series metadata available
#(I couldn't find it anyway)
s = div.find('a',href=re.compile(re.escape("./index.php?act=ovr")+".*$"))
if s is not None:
s['href'] = 'http://'+self.getSiteDomain()+'/fanfictions'+s['href'][1:]
return self.utf8FromSoup(url,div)
+41 -27
View File
@@ -206,6 +206,12 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
# b.extract()
metatext = stripHTML(grayspan).replace('Hurt/Comfort','Hurt-Comfort')
#logger.debug("metatext:(%s)"%metatext)
if 'Status: Complete' in metatext:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
metalist = metatext.split(" - ")
#logger.debug("metalist:(%s)"%metalist)
@@ -240,36 +246,44 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('dateUpdated',datetime.fromtimestamp(float(dates[0]['data-xutime'])))
self.story.setMetadata('datePublished',datetime.fromtimestamp(float(dates[-1]['data-xutime'])))
donechars = False
# Meta key titles and the metadata they go into, if any.
metakeys = {
# These are already handled separately.
'Chapters':False,
'Status':False,
'id':False,
'Updated':False,
'Published':False,
'Reviews':'reviews',
'Favs':'favs',
'Follows':'follows',
'Words':'numWords',
}
chars_ships_list=[]
while len(metalist) > 0:
if metalist[0].startswith('Chapters') or metalist[0].startswith('Status') or metalist[0].startswith('id:') or metalist[0].startswith('Updated:') or metalist[0].startswith('Published:'):
pass
elif metalist[0].startswith('Reviews'):
self.story.setMetadata('reviews',metalist[0].split(':')[1].strip())
elif metalist[0].startswith('Favs:'):
self.story.setMetadata('favs',metalist[0].split(':')[1].strip())
elif metalist[0].startswith('Follows:'):
self.story.setMetadata('follows',metalist[0].split(':')[1].strip())
elif metalist[0].startswith('Words'):
self.story.setMetadata('numWords',metalist[0].split(':')[1].strip())
elif not donechars:
# with 'pairing' support, pairings are bracketed w/o comma after
# [Caspian X, Lucy Pevensie] Edmund Pevensie, Peter Pevensie
self.story.extendList('characters',metalist[0].replace('[','').replace(']',',').split(','))
m = metalist.pop(0)
if ':' in m:
key = m.split(':')[0].strip()
if key in metakeys:
if metakeys[key]:
self.story.setMetadata(metakeys[key],m.split(':')[1].strip())
continue
# no ':' or not found in metakeys
chars_ships_list.append(m)
l = metalist[0]
while '[' in l:
self.story.addToList('ships',l[l.index('[')+1:l.index(']')].replace(', ','/'))
l = l[l.index(']')+1:]
donechars = True
metalist=metalist[1:]
if 'Status: Complete' in metatext:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
# all because sometimes chars can have ' - ' in them.
chars_ships_text = (' - ').join(chars_ships_list)
# print("chars_ships_text:%s"%chars_ships_text)
# with 'pairing' support, pairings are bracketed w/o comma after
# [Caspian X, Lucy Pevensie] Edmund Pevensie, Peter Pevensie
self.story.extendList('characters',chars_ships_text.replace('[','').replace(']',',').split(','))
l = chars_ships_text
while '[' in l:
self.story.addToList('ships',l[l.index('[')+1:l.index(']')].replace(', ','/'))
l = l[l.index(']')+1:]
if get_cover:
# Try the larger image first.
cover_url = ""
+63 -42
View File
@@ -55,7 +55,7 @@ class FicBookNetAdapter(BaseSiteAdapter):
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/readfic/'+self.story.getMetadata('storyId'))
self._setURL('https://' + self.getSiteDomain() + '/readfic/'+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','fbn')
@@ -71,10 +71,10 @@ class FicBookNetAdapter(BaseSiteAdapter):
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/readfic/12345 http://"+cls.getSiteDomain()+"/readfic/93626/246417#part_content"
return "https://"+cls.getSiteDomain()+"/readfic/12345 https://"+cls.getSiteDomain()+"/readfic/93626/246417#part_content"
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/readfic/")+r"\d+"
return r"https?://"+re.escape(self.getSiteDomain()+"/readfic/")+r"\d+"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -92,39 +92,51 @@ class FicBookNetAdapter(BaseSiteAdapter):
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
adult_div = soup.find('div',id='adultCoverWarning')
if adult_div:
if self.is_adult or self.getConfig("is_adult"):
adult_div.extract()
else:
raise exceptions.AdultCheckRequired(self.url)
# Now go hunting for all the meta data and the chapter list.
table = soup.find('td',{'width':'50%'})
## Title
a = soup.find('h1')
a = soup.find('section',{'class':'chapter-info'}).find('h1')
# kill '+' marks if present.
sup = a.find('sup')
if sup:
sup.extract()
self.story.setMetadata('title',stripHTML(a))
logger.debug("Title: (%s)"%self.story.getMetadata('title'))
# Find authorid and URL from... author url.
a = table.find('a')
# assume first avatar-nickname -- there can be a second marked 'beta'.
a = soup.find('a',{'class':'avatar-nickname'})
self.story.setMetadata('authorId',a.text) # Author's name is unique
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
self.story.setMetadata('authorUrl','https://'+self.host+'/'+a['href'])
self.story.setMetadata('author',a.text)
logger.debug("Author: (%s)"%self.story.getMetadata('author'))
# Find the chapters:
chapters = soup.find('div', {'class' : 'part_list'})
chapters = soup.find('ul', {'class' : 'table-of-contents'})
if chapters != None:
chapters=chapters.findAll('a', href=re.compile(r'/readfic/'+self.story.getMetadata('storyId')+"/\d+#part_content$"))
self.story.setMetadata('numChapters',len(chapters))
for x in range(0,len(chapters)):
chapter=chapters[x]
churl='http://'+self.host+chapter['href']
churl='https://'+self.host+chapter['href']
self.chapterUrls.append((stripHTML(chapter),churl))
if x == 0:
pubdate = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
pubdate = translit.translit(stripHTML(chapter.parent.find('span')))
# pubdate = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
if x == len(chapters)-1:
update = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
update = translit.translit(stripHTML(chapter.parent.find('span')))
# update = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
else:
self.chapterUrls.append((self.story.getMetadata('title'),url))
self.story.setMetadata('numChapters',1)
pubdate=translit.translit(stripHTML(soup.find('div', {'class' : 'part_added'}).find('span')))
pubdate=translit.translit(stripHTML(soup.find('div',{'class':'title-area'}).find('span')))
update=pubdate
logger.debug("numChapters: (%s)"%self.story.getMetadata('numChapters'))
@@ -158,54 +170,63 @@ class FicBookNetAdapter(BaseSiteAdapter):
self.story.setMetadata('dateUpdated', makeDate(update, self.dateformat))
self.story.setMetadata('datePublished', makeDate(pubdate, self.dateformat))
self.story.setMetadata('language','Russian')
pr=soup.find('a', href=re.compile(r'/printfic/\w+'))
pr='http://'+self.host+pr['href']
pr = self.make_soup(self._fetchUrl(pr))
pr=pr.findAll('div', {'class' : 'part_text'})
## after site change, I don't see word count anywhere.
# pr=soup.find('a', href=re.compile(r'/printfic/\w+'))
# pr='https://'+self.host+pr['href']
# pr = self.make_soup(self._fetchUrl(pr))
# pr=pr.findAll('div', {'class' : 'part_text'})
# i=0
# for part in pr:
# i=i+len(stripHTML(part).split(' '))
# self.story.setMetadata('numWords', unicode(i))
dlinfo = soup.find('dl',{'class':'info'})
i=0
for part in pr:
i=i+len(stripHTML(part).split(' '))
self.story.setMetadata('numWords', unicode(i))
i=0
fandoms = table.findAll('a', href=re.compile(r'/fanfiction/\w+'))
fandoms = dlinfo.find('dd').findAll('a', href=re.compile(r'/fanfiction/\w+'))
for fandom in fandoms:
self.story.addToList('category',fandom.string)
i=i+1
if i > 1:
self.story.addToList('genre', u'Кроссовер')
meta=table.findAll('a', href=re.compile(r'/ratings/'))
i=0
for m in meta:
if i == 0:
self.story.setMetadata('rating', stripHTML(m))
i=1
elif i == 1:
if not "," in m.nextSibling:
i=2
self.story.addToList('genre', m.find('b').text)
elif i == 2:
self.story.addToList('warnings', m.find('b').text)
if table.find('span', {'style' : 'color: green'}):
for genre in dlinfo.findAll('a',href=re.compile(r'/genres/')):
self.story.addToList('genre',stripHTML(genre))
ratingdt = dlinfo.find('dt',text='Рейтинг:')
self.story.setMetadata('rating', stripHTML(ratingdt.next_sibling))
# meta=table.findAll('a', href=re.compile(r'/ratings/'))
# i=0
# for m in meta:
# if i == 0:
# self.story.setMetadata('rating', stripHTML(m))
# i=1
# elif i == 1:
# if not "," in m.nextSibling:
# i=2
# self.story.addToList('genre', m.find('b').text)
# elif i == 2:
# self.story.addToList('warnings', m.find('b').text)
if dlinfo.find('span', {'style' : 'color: green'}):
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
tags = table.findAll('b')
tags = dlinfo.findAll('dt')
for tag in tags:
label = translit.translit(tag.text)
if 'Piersonazhi:' in label or u'Персонажи:' in label:
chars=tag.nextSibling.string.split(', ')
chars=stripHTML(tag.next_sibling).split(', ')
for char in chars:
self.story.addToList('characters',char)
break
summary=soup.find('span', {'class' : 'urlize'})
summary=soup.find('div', {'class' : 'urlize'})
self.setDescription(url,summary)
#self.story.setMetadata('description', summary.text)
@@ -117,7 +117,7 @@ class FictionManiaTVAdapter(BaseSiteAdapter):
self.story.setMetadata('rating', value)
elif key == 'Complete':
self.story.setMetadata('status', 'Complete' if value == 'Complete' else 'In-Progress')
self.story.setMetadata('status', 'Completed' if value == 'Complete' else 'In-Progress')
elif key == 'Categories':
for element in cells[1]('a'):
+1 -1
View File
@@ -93,7 +93,7 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
try:
data = self._fetchUrl(url)
# non-existent/removed story urls get thrown to the front page.
if "<h2>Welcome to FicWad</h2>" in data:
if "<h4>Featured Story</h4>" in data:
raise exceptions.StoryDoesNotExist(self.url)
soup = self.make_soup(data)
except urllib2.HTTPError, e:
@@ -14,7 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
from base_xenforoforum_adapter import BaseXenForoForumAdapter
@@ -1,309 +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 GrangerEnchantedCom
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class GrangerEnchantedCom(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.
if "malfoymanor" in self.parsedUrl.netloc:
self._setURL('http://malfoymanor.' + self.getSiteDomain() + '/themanor/viewstory.php?sid='+self.story.getMetadata('storyId'))
self.story.addToList("category","The Manor")
else:
self._setURL('http://' + self.getSiteDomain() + '/enchant/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','gech')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%b/%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'grangerenchanted.com'
@classmethod
def getAcceptDomains(cls):
return ['grangerenchanted.com','malfoymanor.grangerenchanted.com']
@classmethod
def getSiteExampleURLs(cls):
return "http://grangerenchanted.com/enchant/viewstory.php?sid=1234 http://malfoymanor.grangerenchanted.com/themanor/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return r"http://(malfoymanor.)?grangerenchanted.com/(enchant|themanor)?/viewstory.php\?sid=\d+$"
## Login seems to be reasonably standard across eFiction sites.
def needToLoginCheck(self, data):
if 'Registered Users Only' in data \
or 'There is no such account on our website' in data \
or "That password doesn't match the one in our database" in data:
return True
else:
return False
def performLogin(self, url):
params = {}
if self.password:
params['penname'] = self.username
params['password'] = self.password
else:
params['penname'] = self.getConfig("username")
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
if "enchant" in self.section:
loginUrl = 'http://grangerenchanted.com/enchant/user.php?action=login'
else:
loginUrl = 'http://malfoymanor.grangerenchanted.com/themanor/user.php?action=login'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['penname']))
d = self._fetchUrl(loginUrl, params)
if "Member Account" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
raise exceptions.FailedToLogin(url,params['penname'])
return False
else:
return True
## 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 = "&ageconsent=ok&warning=1"
else:
addurl=""
# index=1 makes sure we see the story chapter index. Some
# sites skip that for one-chapter stories.
url = self.url+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
if self.needToLoginCheck(data):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url)
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.AccessDenied(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 value and 'label' not in defaultGetattr(value,'class') and '<span class="label">' not in unicode(value):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
if 'Rated' in label:
self.story.setMetadata('rating', value)
if 'Word count' in label:
self.story.setMetadata('numWords', value)
if 'Read' in label:
self.story.setMetadata('read', 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=4'))
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
try:
self.story.setMetadata('reviews',
stripHTML(soup.find('div',{'id':'sort'}).
findAll('a', href=re.compile(r'^reviews.php'))[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' : 'story1'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -188,9 +188,13 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
data = self._fetchUrl(url)
# remove everything after here--the site's chapters break the
# BS4 parser.
data = data[:data.index('<script type="text/javascript" src="reviewjs.js">')]
try:
# remove everything after here--the site's chapters break
# the BS4 parser.
data = data[:data.index('<script type="text/javascript" src="reviewjs.js">')]
except:
# some older stories don't have the code at the end that breaks things.
pass
soup = self.make_soup(data)
+14 -187
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2012 Fanficdownloader team, 2015 FanFicFare team
# Copyright 2012 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.
@@ -15,195 +15,22 @@
# limitations under the License.
#
import time
import logging
logger = logging.getLogger(__name__)
import re
import urllib2
# Software: eFiction
from base_efiction_adapter import BaseEfictionAdapter
class NCISFictionNetAdapter(BaseEfictionAdapter):
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@staticmethod
def getSiteDomain():
return 'ncisfiction.net'
from base_adapter import BaseSiteAdapter, makeDate
@classmethod
def getSiteAbbrev(self):
return 'ncisfn'
@classmethod
def getDateFormat(self):
return "%m/%d/%Y"
def getClass():
return NCISFictionNetAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class NCISFictionNetAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.decode = ["iso-8859-1",
"Windows-1252"] # 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])
# normalized story URL.
self._setURL("http://"+self.getSiteDomain()\
+"/chapters.php?stid="+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ncisfn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'www.ncisfiction.net'
## Changed from www.ncisfiction.com to www.ncisfiction.net Oct
## 2012 due to the ncisfiction.com domain expiring. Still accept
## .com domains for existing updates, etc.
@classmethod
def getAcceptDomains(cls):
return ['www.ncisfiction.net','www.ncisfiction.com']
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/story.php?stid=01234 http://"+cls.getSiteDomain()+"/chapters.php?stid=1234"
def getSiteURLPattern(self):
return r'http://www\.ncisfiction\.(net|com)/(chapters|story)?.php\?stid=\d+'
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
# index=1 makes sure we see the story chapter index. Some
# sites skip that for one-chapter stories.
url = self.url
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
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
raise exceptions.AccessDenied(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 and author
a = soup.find('div', {'class' : 'main_title'})
aut = a.find('a')
self.story.setMetadata('authorId',aut['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
self.story.setMetadata('title',stripHTML(a)[:len(stripHTML(a))-2])
# Find the chapters:
i=0
chapters=soup.findAll('table', {'class' : 'story_table'})
for chapter in chapters:
ch=chapter.find('a')
# just in case there's tags, like <i> in chapter titles.
self.chapterUrls.append((stripHTML(ch),'http://'+self.host+'/'+ch['href']))
if i == 0:
self.story.setMetadata('datePublished', makeDate(stripHTML(chapter.find('td')).split('Added: ')[1], self.dateformat))
if i == len(chapters)-1:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(chapter.find('td')).split('Added: ')[1], self.dateformat))
i=i+1
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.
info = soup.find('table', {'class' : 'story_info'})
# no convenient way to calculate word count as it is logged differently for stories with and without series
labels = info.findAll('tr')
for tr in labels:
value = tr.find('td')
label = tr.find('th').string
if 'Summary' in label:
self.setDescription(url,value)
if 'Rating' in label:
self.story.setMetadata('rating', value.string)
if 'Category' in label:
cats = value.findAll('a')
for cat in cats:
self.story.addToList('category',cat.string)
if 'Characters' in label:
chars = value.findAll('a')
for char in chars:
self.story.addToList('characters',char.string)
if 'Pairing' in label:
ships = value.findAll('a')
for ship in ships:
self.story.addToList('ships',ship.string)
if 'Genre' in label:
genres = value.findAll('a')
for genre in genres:
self.story.addToList('genre',genre.string)
if 'Warnings' in label:
warnings = value.findAll('a')
for warning in warnings:
self.story.addToList('warnings',warning.string)
if 'Status' in label:
if 'not completed' in value.text:
self.story.setMetadata('status', 'In-Progress')
else:
self.story.setMetadata('status', 'Completed')
try:
# Find Series name from series URL.
a = soup.find('div',{'class' : 'sub_header'})
series_name = a.find('a').string
i = a.text.split('#')[1]
self.setSeries(series_name, i)
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+a.find('a')['href'])
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', {'class' : 'story_text'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -1,179 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2013 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 NickAndGregNetAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class NickAndGregNetAdapter(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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/desert_archive/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','nag')
# 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 'www.nickngreg.nl'
@classmethod
def getAcceptDomains(cls):
return ['www.nickngreg.nl','www.nickandgreg.net']
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/desert_archive/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return "http://("+self.getSiteDomain()+"|www.nickandgreg.net)"+re.escape("/desert_archive/viewstory.php?sid=")+r"\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
# index=1 makes sure we see the story chapter index. Some
# sites skip that for one-chapter stories.
url = self.url+'&i=1'
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
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
raise exceptions.AccessDenied(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+'/desert_archive/'+a['href'])
self.story.setMetadata('author',a.string)
# Find the chapters:
chapters = soup.find('select')
for chapter in chapters.findAll('option'):
if chapter.text != 'Story Index' and chapter.text != 'Chapters':
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/desert_archive/'+chapter['value']))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for div in asoup.findAll('td', {'class' : 'tblborder6'}):
a = div.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
if a != None:
break
self.setDescription(url,div.find('br').nextSibling)
a=div.text.split('Rating:')
if len(a) == 2: self.story.setMetadata('rating', a[1].split(' -')[0])
a=div.text.split('Characters:')
if len(a) == 2:
for char in a[1].split(' -')[0].split(', '):
self.story.addToList('characters',char)
a=div.text.split('Genres:')
if len(a) == 2:
for genre in a[1].split(' -')[0].split(', '):
self.story.addToList('genre',genre)
a=div.text.split('Warnings:')
if len(a) == 2:
for warn in a[1].split(' -')[0].split(', '):
if 'none' not in warn:
self.story.addToList('warnings',warn)
a=div.text.split('Completed:')
if len(a) ==2:
if 'Yes' in a[1]:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
a=div.text.split('Published:')
if len(a) == 2: self.story.setMetadata('datePublished', makeDate(stripHTML(a[1].split(' -')[0]), self.dateformat))
a=div.text.split('Updated:')
if len(a) == 2: self.story.setMetadata('dateUpdated', makeDate(stripHTML(a[1].split(' -')[0]), self.dateformat))
# 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))
# wrap a div around it.
divsoup = self.make_soup('<div class="story"></div>')
div = divsoup.find('div')
div.append(soup.find('table', {'class' : 'tblborder6'}))
return self.utf8FromSoup(url,div)
+6 -6
View File
@@ -63,17 +63,17 @@ class QuotevComAdapter(BaseSiteAdapter):
title = element.find('h1')
self.story.setMetadata('title', title.get_text())
authdiv = title.next_sibling # soup.find('div', {'style':"font-size:0.7em;color:#aaa;margin-top:-10px;text-align:center;margin-left:45px;cursor:pointer"})
authdiv = soup.find('div', {'class':"quizAuthorList"})
if authdiv:
#print("div:%s"%authdiv.find_all('a'))
print("div:%s"%authdiv)
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')
if not self.story.getList('author'):
self.story.addToList('author','Anonymous')
self.story.addToList('authorUrl','http://www.quotev.com')
self.story.addToList('authorId','0')
self.setDescription(self.url, soup.find('div', id='qdesct'))
imgmeta = soup.find('meta',{'property':"og:image" })
@@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 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.
# 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
from base_efiction_adapter import BaseEfictionAdapter
class RabidReaderComAdapter(BaseEfictionAdapter):
@staticmethod
def getSiteDomain():
return 'www.therabidreader.com'
@classmethod
def getSiteAbbrev(self):
return 'rrcom'
@classmethod
def getDateFormat(self):
return "%d %b %Y"
def getClass():
return RabidReaderComAdapter
-299
View File
@@ -1,299 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2013 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 ScarHeadNetAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class ScarHeadNetAdapter(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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','shn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'scarhead.net'
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
## Login seems to be reasonably standard across eFiction sites.
def needToLoginCheck(self, data):
if 'Registered Users Only' in data \
or 'There is no such account on our website' in data \
or "That password doesn't match the one in our database" in data:
return True
else:
return False
def performLogin(self, url):
params = {}
if self.password:
params['penname'] = self.username
params['password'] = self.password
else:
params['penname'] = self.getConfig("username")
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['penname']))
d = self._fetchUrl(loginUrl, params)
if "Member Account" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
raise exceptions.FailedToLogin(url,params['penname'])
return False
else:
return True
## 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 = "&ageconsent=ok&warning=5"
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
if self.needToLoginCheck(data):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url)
# Since the warning text can change by warning level, let's
# look for the warning pass url. ksarchive uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
#m = re.search(r"'viewstory.php\?sid=1882(&amp;warning=4)'",data)
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.AccessDenied(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.
pagetitle = soup.find('tr',{'valign':'top'})
## Title
a = pagetitle.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 = pagetitle.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+'/'+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.
cats = soup.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for cat in cats:
if '/' == cat.string[0]:
self.story.addToList('ships','Harry Potter'+cat.string.split('(')[0])
elif 'Harry' in cat.string:
self.story.addToList('ships',cat.string.split('(')[0])
else:
self.story.addToList('category',cat.string)
if '(' in cat.string:
self.story.addToList('category',cat.string.split('(')[1].split(')')[0])
chars = soup.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
self.story.addToList('characters',char.string)
genres = soup.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
for genre in genres:
self.story.addToList('genre',genre.string)
warnings = soup.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
for warning in warnings:
self.story.addToList('warnings',warning.string)
textsoup = stripHTML(soup)
a = textsoup.split('Published: ')[1].split(' ')[0]
self.story.setMetadata('datePublished', makeDate(stripHTML(a), self.dateformat))
a = textsoup.split('Updated: ')[1].split(' ')[0]
self.story.setMetadata('dateUpdated', makeDate(stripHTML(a), self.dateformat))
a = textsoup.split('Rating: ')[1].split(' ')[0]
self.story.setMetadata('rating', a)
a = textsoup.split('Length: ')[1].split('(')[1].split(' ')[0]
self.story.setMetadata('numWords', a)
a = textsoup.split('Completed: ')[1].split(' ')[0]
if 'Yes' in a:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
#a = textsoup.split('Summary: ')[1].split('Add Story to Favorites')[0]
#self.setDescription(url,a)
a=soup.find(text=re.compile("Summary: "))
i=0
svalue = ""
while i == 0:
try:
b = unicode(a)
svalue += b.split('Summary: ')[1]
except:
svalue += unicode(a)
if a.nextSibling != None:
a = a.nextSibling
else:
a = a.parent.nextSibling
if 'Disclaimer: ' in stripHTML(a):
i=1
self.setDescription(url,svalue)
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+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
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)
+5
View File
@@ -112,6 +112,11 @@ class SiyeCoUkAdapter(BaseSiteAdapter): # XXX
# need(or easier) to pull other metadata from the author's list page.
authsoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
# remove author profile incase they've put the story URL in their bio.
profile = authsoup.find('div',{'id':'profile'})
if profile: # in case it changes.
profile.extract()
## Title
titlea = authsoup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(titlea))
+19 -19
View File
@@ -97,7 +97,7 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
params['page'] = 'http://'+self.getSiteDomain()+'/'
params['submit'] = 'Login'
loginUrl = 'https://' + self.getSiteDomain() + '/login.php'
loginUrl = 'https://' + self.getSiteDomain() + '/sol-secure/login.php'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['theusername']))
@@ -211,7 +211,7 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
try:
a = lc4.find('a', href=re.compile(r"/series/\d+/.*"))
logger.debug("Looking for series - a='{0}'".format(a))
# logger.debug("Looking for series - a='{0}'".format(a))
if a:
# if there's a number after the series name, series_contents is a two element list:
# [<a href="...">Title</a>, u' (2)']
@@ -220,34 +220,34 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
seriesUrl = 'http://'+self.host+a['href']
self.story.setMetadata('seriesUrl',seriesUrl)
series_name = stripHTML(a)
logger.debug("Series name= %s" % series_name)
# logger.debug("Series name= %s" % series_name)
series_soup = self.make_soup(self._fetchUrl(seriesUrl))
if series_soup:
logger.debug("Retrieving Series - looking for name")
# logger.debug("Retrieving Series - looking for name")
series_name = stripHTML(series_soup.find('span', {'id' : 'ptitle'}))
series_name = re.sub(r' . a series by.*$','',series_name)
logger.debug("Series name: '{0}'".format(series_name))
# logger.debug("Series name: '%s'" % series_name)
self.setSeries(series_name, i)
desc = lc4.contents[2]
# Check if series is in a universe
universe_url = self.story.getList('authorUrl')[0] + "&type=uni"
universes_soup = self.make_soup(self._fetchUrl(universe_url) )
logger.debug("Universe url='{0}'".format(universe_url))
# logger.debug("Universe url='{0}'".format(universe_url))
if universes_soup:
universes = universes_soup.findAll('div', {'class' : 'ser-box'})
logger.debug("Number of Universes: %d" % len(universes))
# logger.debug("Number of Universes: %d" % len(universes))
for universe in universes:
logger.debug("universe.find('a')={0}".format(universe.find('a')))
# logger.debug("universe.find('a')={0}".format(universe.find('a')))
# The universe id is in an "a" tag that has an id but nothing else. It is the first tag.
# The id is prefixed with the letter "u".
universe_id = universe.find('a')['id'][1:]
logger.debug("universe_id='%s'" % universe_id)
# logger.debug("universe_id='%s'" % universe_id)
universe_name = stripHTML(universe.find('div', {'class' : 'ser-name'})).partition(' ')[2]
logger.debug("universe_name='%s'" % universe_name)
# logger.debug("universe_name='%s'" % universe_name)
# If there is link to the story, we have the right universe
story_a = universe.find('a', href=re.compile('/s/'+self.story.getMetadata('storyId')))
if story_a:
logger.debug("Story is in a series that is in a universe! The universe is '%s'" % universe_name)
# logger.debug("Story is in a series that is in a universe! The universe is '%s'" % universe_name)
self.story.setMetadata("universe", universe_name)
self.story.setMetadata('universeUrl','http://'+self.host+ '/library/universe.php?id=' + universe_id)
break
@@ -258,24 +258,24 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
pass
try:
a = lc4.find('a', href=re.compile(r"/universe/\d+/.*"))
logger.debug("Looking for universe - a='{0}'".format(a))
# logger.debug("Looking for universe - a='{0}'".format(a))
if a:
self.story.setMetadata("universe",stripHTML(a))
desc = lc4.contents[2]
# Assumed only one universe, but it does have a URL--use universeHTML
universe_name = stripHTML(a)
universeUrl = 'http://'+self.host+a['href']
logger.debug("Retrieving Universe - about to get page - universeUrl='{0}".format(universeUrl))
# logger.debug("Retrieving Universe - about to get page - universeUrl='{0}".format(universeUrl))
universe_soup = self.make_soup(self._fetchUrl(universeUrl))
logger.debug("Retrieving Universe - have page")
if universe_soup:
logger.debug("Retrieving Universe - looking for name")
universe_name = stripHTML(universe_soup.find('h1', {'id' : 'ptitle'}))
universe_name = re.sub(r' . A Universe from the Mind.*$','',universe_name)
logger.debug("Universes name: '{0}'".format(universe_name))
# logger.debug("Universes name: '{0}'".format(universe_name))
self.story.setMetadata('universeUrl',universeUrl)
logger.debug("Setting universe name: '{0}'".format(universe_name))
# logger.debug("Setting universe name: '{0}'".format(universe_name))
self.story.setMetadata('universe',universe_name)
if self.getConfig("universe_as_series"):
self.setSeries(universe_name, 0)
@@ -320,12 +320,12 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
# http://storiesonline.net/s/11999
# http://storiesonline.net/s/10823
if get_cover:
logger.debug("Looking for the cover image...")
# logger.debug("Looking for the cover image...")
cover_url = ""
img = soup.find('img')
if img:
cover_url=img['src']
logger.debug("cover_url: %s"%cover_url)
# logger.debug("cover_url: %s"%cover_url)
if cover_url:
self.setCoverImage(url,cover_url)
@@ -363,7 +363,7 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
urls=pager.findAll('a')
urls=urls[:len(urls)-1]
logger.debug("pager urls:%s"%urls)
# logger.debug("pager urls:%s"%urls)
pager.extract()
chaptertag.contents = chaptertag.contents[2:]
@@ -372,7 +372,7 @@ class StoriesOnlineNetAdapter(BaseSiteAdapter):
pagetag = soup.find('div', {'id' : 'story'})
if not pagetag:
logger.debug("div id=story not found, try article")
# logger.debug("div id=story not found, try article")
pagetag = soup.find('article', {'id' : 'story'})
self.cleanPage(pagetag)
@@ -1,238 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2013 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 TokraFandomnetComAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class TokraFandomnetComAdapter(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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','tokra')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%m/%d/%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it. But it
# doesn't matter too much anymore.
return 'tokra.fandomnet.com'
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\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 = "&ageconsent=ok&warning=3"
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.AccessDenied(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)
# Rating
rate = stripHTML(soup.find('div',{'id':'pagetitle'}))
rate = rate[rate.rindex('[')+1:rate.rindex(']')]
self.story.setMetadata('rating', rate)
# 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+'/'+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.
metadiv = soup.find('div',{'class':'content'})
smalldiv = metadiv.find('div',{'class':'small'})
# tokra categories -> genre
# categories will be filled from ini.
genres = smalldiv.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for genre in genres:
self.story.addToList('genre',genre.string)
chars = smalldiv.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
self.story.addToList('characters',char.string)
metatext = stripHTML(smalldiv)
if 'Completed: Yes' in metatext:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
wordstart=metatext.rindex('Word count:')+12
words = metatext[wordstart:metatext.index(' ',wordstart)]
self.story.setMetadata('numWords', words)
datesdiv = soup.find('div',{'class':'bottom'})
dates = stripHTML(datesdiv).split()
# Published: 04/26/2011 Updated: 03/06/2013
self.story.setMetadata('datePublished', makeDate(dates[1], self.dateformat))
self.story.setMetadata('dateUpdated', makeDate(dates[3], 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+'/'+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
# remove 'small' leaving only summary.
smalldiv.extract()
self.setDescription(url,metadiv)
# 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', {'class' : 'content'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
# remove some decorations while keeping notes.
remove = div.find('div', {'id' : 'pagetitle'})
remove.extract()
for remove in div.findAll('div', {'class' : 'right'}):
remove.extract()
for remove in div.findAll('div', {'class' : 'left'}):
remove.extract()
return self.utf8FromSoup(url,div)
+21 -5
View File
@@ -16,7 +16,8 @@
#
import re
import datetime
from datetime import datetime, timedelta
import time
import logging
import urllib
@@ -99,7 +100,7 @@ class BaseSiteAdapter(Configurable):
self.metadataDone = False
self.story = Story(configuration)
self.story.setMetadata('site',self.getConfigSection())
self.story.setMetadata('dateCreated',datetime.datetime.now())
self.story.setMetadata('dateCreated',datetime.now())
self.chapterUrls = [] # tuples of (chapter title,chapter url)
self.chapterFirst = None
self.chapterLast = None
@@ -131,9 +132,9 @@ class BaseSiteAdapter(Configurable):
def set_cookiejar(self,cj):
self.cookiejar = cj
saveheaders = self.opener.addheaders
self.opener = u2.build_opener(u2.HTTPCookieProcessor(self.cookiejar),GZipProcessor())
self.opener.addheaders = [('User-Agent', self.getConfig('user_agent')),
('X-Clacks-Overhead','GNU Terry Pratchett')]
self.opener.addheaders = saveheaders
def load_cookiejar(self,filename):
'''
@@ -688,8 +689,23 @@ def makeDate(string,dateform):
if name in string:
string = string.replace(name,num)
break
# Many locales don't define %p for AM/PM. So if %p, remove from
# dateform, look for 'pm' in string, remove am/pm from string and
# add 12 hours if pm found.
add_hours = False
if u"%p" in dateform:
dateform = dateform.replace(u"%p",u"")
if 'pm' in string or 'PM' in string:
add_hours = True
string = string.replace(u"AM",u"").replace(u"PM",u"").replace(u"am",u"").replace(u"pm",u"")
date = datetime.strptime(string.encode('utf-8'),dateform.encode('utf-8'))
if add_hours:
date += timedelta(hours=12)
return datetime.datetime.strptime(string.encode('utf-8'),dateform.encode('utf-8'))
return date
# .? for AO3's ']' in param names.
safe_url_re = re.compile(r'(?P<attr>(password|name|login).?=)[^&]*(?P<amp>&|$)',flags=re.MULTILINE)
@@ -196,8 +196,8 @@ class BaseXenForoForumAdapter(BaseSiteAdapter):
soup = soup.find('li',{'class':'message'}) # limit first post for date stuff below. ('#' posts above)
if threadmark_chaps or self.getConfig('always_use_forumtags'):
## only use tags if threadmarks for chapters or
for tag in topsoup.findAll('a',{'class':'tag'}):
## only use tags if threadmarks for chapters or always_use_forumtags is on.
for tag in topsoup.findAll('a',{'class':'tag'}) + topsoup.findAll('span',{'class':'prefix'}):
tstr = stripHTML(tag)
if self.getConfig('capitalize_forumtags'):
tstr = tstr.title()
@@ -228,11 +228,18 @@ class BaseXenForoForumAdapter(BaseSiteAdapter):
if ( url.startswith(self.getURLPrefix()) or
url.startswith('http://'+self.getSiteDomain()) or
url.startswith('https://'+self.getSiteDomain()) ) and ('/posts/' in url or '/threads/' in url):
url.startswith('https://'+self.getSiteDomain()) ) and \
( '/posts/' in url or '/threads/' in url or 'showpost.php' in url or 'goto/post' in url):
# brute force way to deal with SB's http->https change when hardcoded http urls.
url = url.replace('http://'+self.getSiteDomain(),self.getURLPrefix())
# http://forums.spacebattles.com/showpost.php?p=4755532&postcount=9
url = re.sub(r'showpost\.php\?p=([0-9]+)(&postcount=[0-9]+)?',r'/posts/\1/',url)
# http://forums.spacebattles.com/goto/post?id=15222406#post-15222406
url = re.sub(r'/goto/post\?id=([0-9]+)(#post-[0-9]+)?',r'/posts/\1/',url)
url = re.sub(r'(^[\'"]+|[\'"]+$)','',url) # strip leading or trailing '" from incorrect quoting.
url = re.sub(r'like$','',url) # strip 'like' if incorrect 'like' link instead of proper post URL.
@@ -272,7 +279,7 @@ class BaseXenForoForumAdapter(BaseSiteAdapter):
datestr = re.sub(r' (\d[^\d])',r' 0\1',datestr) # add leading 0 for single digit day & hours.
return makeDate(datestr, self.dateformat)
except:
logger.debug('No date found in %s'%parenttag)
logger.debug('No date found in %s'%parenttag,exc_info=True)
return None
# grab the text for an individual chapter.
+12 -90
View File
@@ -473,8 +473,8 @@ add_to_include_subject_tags:,tagsfromtitle.SPLIT,forumtags
## base_xenforoforum reads Published and Updated datetimes from
## Threadmarks if used, or from the posted & updated times of the
## 'first' post if no threadmarks.
datePublished_format:%%Y-%%m-%%d %%H:%%M:%%S
dateUpdated_format:%%Y-%%m-%%d %%H:%%M:%%S
datePublished_format:%%Y-%%m-%%d %%H:%%M
dateUpdated_format:%%Y-%%m-%%d %%H:%%M
## Only take the first X characters of the 'first' post to use as
## the description.
@@ -853,18 +853,6 @@ extracategories:The Sentinel
## this should go in your personal.ini, not defaults.ini.
#is_adult:true
[bdsm-geschichten.net]
## 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
## This site offers no index page so we can either guess the chapter URLs
## by dec/incrementing numbers ('guess') or walk all the chapters in the metadata
## parsing state ('parse'). Since guessing can lead to errors for non-standard
## story URLs, the default is to parse
#find_chapters:guess
[bloodshedverse.com]
## website encoding(s) In theory, each website reports the character
## encoding they use for each page. In practice, some sites report it
@@ -875,6 +863,12 @@ extracategories:The Sentinel
## it has +90% confidence. 'auto' is not reliable.
website_encodings:Windows-1252,ISO-8859-1,auto
## dateUpdate doesn't usually have time, but it does on
## bloodshedverse.com. See
## http://docs.python.org/library/datetime.html#strftime-strptime-behavior
## Note that ini format requires % to be escaped as %%.
dateUpdated_format:%%Y-%%m-%%d %%H:%%M
## Extra metadata that this adapter knows about. See [dramione.org]
## for examples of how to use them.
extra_valid_entries:warnings,reviews
@@ -904,15 +898,6 @@ strip_text_links:true
## Site dedicated to these categories/characters/ships
extracategories:Blood Ties
[buffynfaith.net]
## Site dedicated to these categories/characters/ships
extracategories:Buffy: The Vampire Slayer
## 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.
#is_adult:true
[fanfic.castletv.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -1250,27 +1235,6 @@ cover_exclusion_regexp:/css/bir.png
[forums.sufficientvelocity.com]
## see [base_xenforoforum]
[grangerenchanted.com]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
## 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.
#is_adult:true
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
extracharacters:Hermione Granger
## Extra metadata that this adapter knows about. See [dramione.org]
## for examples of how to use them.
extra_valid_entries:read,reviews
[hlfiction.net]
## Site dedicated to these categories/characters/ships
extracategories:Highlander
@@ -1518,22 +1482,6 @@ extracategories:Supernatural
extracharacters:Sam,Dean
extraships:Sam/Dean
[scarhead.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
## 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.
#is_adult:true
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[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
@@ -1675,15 +1623,6 @@ readings_label: Readings
## personal.ini, not defaults.ini.
#is_adult:true
[tokra.fandomnet.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
## Site dedicated to these categories/characters/ships
extracategories:Stargate: SG-1
[tolkienfanfiction.com]
## Site dedicated to these categories/characters/ships
extracategories:Lord of the Rings
@@ -1836,6 +1775,10 @@ check_next_chapter:false
#password:yourpassword
[www.ficbook.net]
## 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
[www.fictionalley.org]
## Some sites do not require a login, but do require the user to
@@ -2041,11 +1984,6 @@ cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
## Site dedicated to these categories/characters/ships
extracategories:NCIS
[www.nickngreg.nl]
## Site dedicated to these categories/characters/ships
extracategories:CSI
extraships:Nick Stokes/Greg Sanders
[www.phoenixsong.net]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -2339,20 +2277,6 @@ extracategories:Andromeda
## Site dedicated to these categories/characters/ships
extracategories:Artemis Fowl
[www.therabidreader.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
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
## commandline version, this should go in your personal.ini, not
## defaults.ini.
#username:YourName
#password:yourpassword
[www.naiceanilme.net]
## Some sites do not require a login, but do require the user to
## confirm they are adult for adult content. In commandline version,
@@ -2366,8 +2290,6 @@ extracategories:Artemis Fowl
#username:YourName
#password:yourpassword
[overrides]
## It may sometimes be useful to override all of the specific format,
## site and site:format sections in your private configuration. For
+2 -4
View File
@@ -84,8 +84,7 @@ def get_update_data(inputio,
oldcover = (oldcoverhtmlhref,oldcoverhtmltype,oldcoverhtmldata,oldcoverimghref,oldcoverimgtype,oldcoverimgdata)
except Exception as e:
logger.warn("Cover Image %s not found"%src)
logger.warn("Exception: %s"%(unicode(e)))
traceback.print_exc()
logger.warn("Exception: %s"%(unicode(e)),exc_info=True)
filecount = 0
soups = [] # list of xhmtl blocks
@@ -127,8 +126,7 @@ def get_update_data(inputio,
# originally.
if newsrc != u'OEBPS/failedtoload':
logger.warn("Image %s not found!\n(originally:%s)"%(newsrc,longdesc))
logger.warn("Exception: %s"%(unicode(e)))
traceback.print_exc()
logger.warn("Exception: %s"%(unicode(e)),exc_info=True)
bodysoup = soup.find('body')
# ffdl epubs have chapter title h3
h3 = bodysoup.find('h3')
-453
View File
@@ -1,453 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""html2text: Turn HTML into equivalent Markdown-structured text."""
__version__ = "2.37"
__author__ = "Aaron Swartz (me@aaronsw.com)"
__copyright__ = "(C) 2004-2008 Aaron Swartz. GNU GPL 3."
__contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
# TODO:
# Support decoded entities with unifiable.
if not hasattr(__builtins__, 'True'): True, False = 1, 0
import re, sys, urllib, htmlentitydefs, codecs, StringIO, types
import sgmllib
import urlparse
sgmllib.charref = re.compile('&#([xX]?[0-9a-fA-F]+)[^0-9a-fA-F]')
try: from textwrap import wrap
except: pass
# Use Unicode characters instead of their ascii psuedo-replacements
UNICODE_SNOB = 0
# Put the links after each paragraph instead of at the end.
LINKS_EACH_PARAGRAPH = 0
# Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
BODY_WIDTH = 78
# Don't show internal links (href="#local-anchor") -- corresponding link targets
# won't be visible in the plain text file anyway.
SKIP_INTERNAL_LINKS = False
### Entity Nonsense ###
def name2cp(k):
if k == 'apos': return ord("'")
if hasattr(htmlentitydefs, "name2codepoint"): # requires Python 2.3
return htmlentitydefs.name2codepoint[k]
else:
k = htmlentitydefs.entitydefs[k]
if k.startswith("&#") and k.endswith(";"): return int(k[2:-1]) # not in latin-1
return ord(codecs.latin_1_decode(k)[0])
unifiable = {'rsquo':"'", 'lsquo':"'", 'rdquo':'"', 'ldquo':'"',
'copy':'(C)', 'mdash':'--', 'nbsp':' ', 'rarr':'->', 'larr':'<-', 'middot':'*',
'ndash':'-', 'oelig':'oe', 'aelig':'ae',
'agrave':'a', 'aacute':'a', 'acirc':'a', 'atilde':'a', 'auml':'a', 'aring':'a',
'egrave':'e', 'eacute':'e', 'ecirc':'e', 'euml':'e',
'igrave':'i', 'iacute':'i', 'icirc':'i', 'iuml':'i',
'ograve':'o', 'oacute':'o', 'ocirc':'o', 'otilde':'o', 'ouml':'o',
'ugrave':'u', 'uacute':'u', 'ucirc':'u', 'uuml':'u'}
unifiable_n = {}
for k in unifiable.keys():
unifiable_n[name2cp(k)] = unifiable[k]
def charref(name):
if name[0] in ['x','X']:
c = int(name[1:], 16)
else:
c = int(name)
if not UNICODE_SNOB and c in unifiable_n.keys():
return unifiable_n[c]
else:
return unichr(c)
def entityref(c):
if not UNICODE_SNOB and c in unifiable.keys():
return unifiable[c]
else:
try: name2cp(c)
except KeyError: return "&" + c
else: return unichr(name2cp(c))
def replaceEntities(s):
s = s.group(1)
if s[0] == "#":
return charref(s[1:])
else: return entityref(s)
r_unescape = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
def unescape(s):
return r_unescape.sub(replaceEntities, s)
def fixattrs(attrs):
# Fix bug in sgmllib.py
if not attrs: return attrs
newattrs = []
for attr in attrs:
newattrs.append((attr[0], unescape(attr[1])))
return newattrs
### End Entity Nonsense ###
def onlywhite(line):
"""Return true if the line does only consist of whitespace characters."""
for c in line:
if c is not ' ' and c is not ' ':
return c is ' '
return line
def optwrap(text,wrap_width=BODY_WIDTH):
"""Wrap all paragraphs in the provided text."""
if not wrap_width:
return text
assert wrap, "Requires Python 2.3."
result = ''
newlines = 0
for para in text.split("\n"):
if len(para) > 0:
if para[0] is not ' ' and para[0] is not '-' and para[0] is not '*':
for line in wrap(para, wrap_width):
result += line + "\n"
result += "\n"
newlines = 2
else:
if not onlywhite(para):
result += para + "\n"
newlines = 1
else:
if newlines < 2:
result += "\n"
newlines += 1
return result
def hn(tag):
if tag[0] == 'h' and len(tag) == 2:
try:
n = int(tag[1])
if n in range(1, 10): return n
except ValueError: return 0
class _html2text(sgmllib.SGMLParser):
def __init__(self, out=None, baseurl=''):
sgmllib.SGMLParser.__init__(self)
if out is None: self.out = self.outtextf
else: self.out = out
self.outtext = u''
self.quiet = 0
self.p_p = 0
self.outcount = 0
self.start = 1
self.space = 0
self.a = []
self.astack = []
self.acount = 0
self.list = []
self.blockquote = 0
self.pre = 0
self.startpre = 0
self.lastWasNL = 0
self.abbr_title = None # current abbreviation definition
self.abbr_data = None # last inner HTML (for abbr being defined)
self.abbr_list = {} # stack of abbreviations to write later
self.baseurl = baseurl
def outtextf(self, s):
self.outtext += s
def close(self):
sgmllib.SGMLParser.close(self)
self.pbr()
self.o('', 0, 'end')
return self.outtext
def handle_charref(self, c):
self.o(charref(c))
def handle_entityref(self, c):
self.o(entityref(c))
def unknown_starttag(self, tag, attrs):
self.handle_tag(tag, attrs, 1)
def unknown_endtag(self, tag):
self.handle_tag(tag, None, 0)
def previousIndex(self, attrs):
""" returns the index of certain set of attributes (of a link) in the
self.a list
If the set of attributes is not found, returns None
"""
if not attrs.has_attr('href'): return None
i = -1
for a in self.a:
i += 1
match = 0
if a.has_attr('href') and a['href'] == attrs['href']:
if a.has_attr('title') or attrs.has_attr('title'):
if (a.has_attr('title') and attrs.has_attr('title') and
a['title'] == attrs['title']):
match = True
else:
match = True
if match: return i
def handle_tag(self, tag, attrs, start):
attrs = fixattrs(attrs)
if hn(tag):
self.p()
if start: self.o(hn(tag)*"#" + ' ')
if tag in ['p', 'div']: self.p()
if tag == "br" and start: self.o(" \n")
if tag == "hr" and start:
self.p()
self.o("* * *")
self.p()
if tag in ["head", "style", 'script']:
if start: self.quiet += 1
else: self.quiet -= 1
if tag in ["body"]:
self.quiet = 0 # sites like 9rules.com never close <head>
if tag == "blockquote":
if start:
self.p(); self.o('> ', 0, 1); self.start = 1
self.blockquote += 1
else:
self.blockquote -= 1
self.p()
if tag in ['em', 'i', 'u']: self.o("_")
if tag in ['strong', 'b']: self.o("**")
if tag == "code" and not self.pre: self.o('`') #TODO: `` `this` ``
if tag == "abbr":
if start:
attrsD = {}
for (x, y) in attrs: attrsD[x] = y
attrs = attrsD
self.abbr_title = None
self.abbr_data = ''
if attrs.has_attr('title'):
self.abbr_title = attrs['title']
else:
if self.abbr_title != None:
self.abbr_list[self.abbr_data] = self.abbr_title
self.abbr_title = None
self.abbr_data = ''
if tag == "a":
if start:
attrsD = {}
for (x, y) in attrs: attrsD[x] = y
attrs = attrsD
if attrs.has_attr('href') and not (SKIP_INTERNAL_LINKS and attrs['href'].startswith('#')):
self.astack.append(attrs)
self.o("[")
else:
self.astack.append(None)
else:
if self.astack:
a = self.astack.pop()
if a:
i = self.previousIndex(a)
if i is not None:
a = self.a[i]
else:
self.acount += 1
a['count'] = self.acount
a['outcount'] = self.outcount
self.a.append(a)
self.o("][" + `a['count']` + "]")
if tag == "img" and start:
attrsD = {}
for (x, y) in attrs: attrsD[x] = y
attrs = attrsD
if attrs.has_attr('src'):
attrs['href'] = attrs['src']
alt = attrs.get('alt', '')
i = self.previousIndex(attrs)
if i is not None:
attrs = self.a[i]
else:
self.acount += 1
attrs['count'] = self.acount
attrs['outcount'] = self.outcount
self.a.append(attrs)
self.o("![")
self.o(alt)
self.o("]["+`attrs['count']`+"]")
if tag == 'dl' and start: self.p()
if tag == 'dt' and not start: self.pbr()
if tag == 'dd' and start: self.o(' ')
if tag == 'dd' and not start: self.pbr()
if tag in ["ol", "ul"]:
if start:
self.list.append({'name':tag, 'num':0})
else:
if self.list: self.list.pop()
self.p()
if tag == 'li':
if start:
self.pbr()
if self.list: li = self.list[-1]
else: li = {'name':'ul', 'num':0}
self.o(" "*len(self.list)) #TODO: line up <ol><li>s > 9 correctly.
if li['name'] == "ul": self.o("* ")
elif li['name'] == "ol":
li['num'] += 1
self.o(`li['num']`+". ")
self.start = 1
else:
self.pbr()
if tag in ["table", "tr"] and start: self.p()
if tag == 'td': self.pbr()
if tag == "pre":
if start:
self.startpre = 1
self.pre = 1
else:
self.pre = 0
self.p()
def pbr(self):
if self.p_p == 0: self.p_p = 1
def p(self): self.p_p = 2
def o(self, data, puredata=0, force=0):
if self.abbr_data is not None: self.abbr_data += data
if not self.quiet:
if puredata and not self.pre:
data = re.sub('\s+', ' ', data)
if data and data[0] == ' ':
self.space = 1
data = data[1:]
if not data and not force: return
if self.startpre:
#self.out(" :") #TODO: not output when already one there
self.startpre = 0
bq = (">" * self.blockquote)
if not (force and data and data[0] == ">") and self.blockquote: bq += " "
if self.pre:
bq += " "
data = data.replace("\n", "\n"+bq)
if self.start:
self.space = 0
self.p_p = 0
self.start = 0
if force == 'end':
# It's the end.
self.p_p = 0
self.out("\n")
self.space = 0
if self.p_p:
self.out(('\n'+bq)*self.p_p)
self.space = 0
if self.space:
if not self.lastWasNL: self.out(' ')
self.space = 0
if self.a and ((self.p_p == 2 and LINKS_EACH_PARAGRAPH) or force == "end"):
if force == "end": self.out("\n")
newa = []
for link in self.a:
if self.outcount > link['outcount']:
self.out(" ["+`link['count']`+"]: " + urlparse.urljoin(self.baseurl, link['href']))
if link.has_attr('title'): self.out(" ("+link['title']+")")
self.out("\n")
else:
newa.append(link)
if self.a != newa: self.out("\n") # Don't need an extra line when nothing was done.
self.a = newa
if self.abbr_list and force == "end":
for abbr, definition in self.abbr_list.items():
self.out(" *[" + abbr + "]: " + definition + "\n")
self.p_p = 0
self.out(data)
self.lastWasNL = data and data[-1] == '\n'
self.outcount += 1
def handle_data(self, data):
if r'\/script>' in data: self.quiet -= 1
self.o(data, 1)
def unknown_decl(self, data): pass
def wrapwrite(text): sys.stdout.write(text.encode('utf8'))
def html2text_file(html, out=wrapwrite, baseurl=''):
h = _html2text(out, baseurl)
h.feed(html)
h.feed("")
return h.close()
def html2text(html, baseurl='', wrap_width=BODY_WIDTH):
return optwrap(html2text_file(html, None, baseurl),wrap_width)
if __name__ == "__main__":
baseurl = ''
if sys.argv[1:]:
arg = sys.argv[1]
if arg.startswith('http://'):
baseurl = arg
j = urllib.urlopen(baseurl)
try:
from feedparser import _getCharacterEncoding as enc
except ImportError:
enc = lambda x, y: ('utf-8', 1)
text = j.read()
encoding = enc(j.headers, text)[0]
if encoding == 'us-ascii': encoding = 'utf-8'
data = text.decode(encoding)
else:
encoding = 'utf8'
if len(sys.argv) > 2:
encoding = sys.argv[2]
data = open(arg, 'r').read().decode(encoding)
else:
data = sys.stdin.read().decode('utf8')
wrapwrite(html2text(data, baseurl))
+3
View File
@@ -1069,6 +1069,9 @@ class Story(Configurable):
if imgurl not in self.imgurls:
try:
if imgurl == 'failedtoload':
raise Exception("Previously failed to load")
parsedUrl = urlparse.urlparse(imgurl)
if self.getConfig('no_image_processing'):
(data,ext,mime) = no_convert_image(imgurl,
+3 -3
View File
@@ -21,7 +21,7 @@ from textwrap import wrap
from base_writer import *
from ..html2text import html2text
from html2text import html2text
## In BaseStoryWriter, we define _write to encode <unicode> objects
## back into <string> for true output. But txt needs to write the
@@ -109,7 +109,7 @@ End file.
self.wrap_width = self.getConfig('wrap_width')
if self.wrap_width == '' or self.wrap_width == '0':
self.wrap_width = None
self.wrap_width = 0
else:
self.wrap_width = int(self.wrap_width)
@@ -159,7 +159,7 @@ End file.
logging.debug('Writing chapter text for: %s' % chap.title)
vals={'url':chap.url, 'chapter':chap.title, 'index':"%04d"%(index+1), 'number':index+1}
self._write(out,self.lineends(self.wraplines(removeAllEntities(CHAPTER_START.substitute(vals)))))
self._write(out,self.lineends(html2text(chap.html,wrap_width=self.wrap_width)))
self._write(out,self.lineends(html2text(chap.html,bodywidth=self.wrap_width)))
self._write(out,self.lineends(self.wraplines(removeAllEntities(CHAPTER_END.substitute(vals)))))
self._write(out,self.lineends(self.wraplines(FILE_END.substitute(self.story.getAllMetadata()))))
+857
View File
@@ -0,0 +1,857 @@
#!/usr/bin/env python
# coding: utf-8
"""html2text: Turn HTML into equivalent Markdown-structured text."""
from __future__ import division
import re
import sys
import cgi
try:
from textwrap import wrap
except ImportError: # pragma: no cover
pass
from html2text.compat import urlparse, HTMLParser
from html2text import config
from html2text.utils import (
name2cp,
unifiable_n,
google_text_emphasis,
google_fixed_width_font,
element_style,
hn,
google_has_height,
escape_md,
google_list_style,
list_numbering_start,
dumb_css_parser,
escape_md_section,
skipwrap
)
__version__ = (2016, 4, 2)
# TODO:
# Support decoded entities with UNIFIABLE.
class HTML2Text(HTMLParser.HTMLParser):
def __init__(self, out=None, baseurl='', bodywidth=config.BODY_WIDTH):
"""
Input parameters:
out: possible custom replacement for self.outtextf (which
appends lines of text).
baseurl: base URL of the document we process
"""
kwargs = {}
if sys.version_info >= (3, 4):
kwargs['convert_charrefs'] = False
HTMLParser.HTMLParser.__init__(self, **kwargs)
# Config options
self.split_next_td = False
self.td_count = 0
self.table_start = False
self.unicode_snob = config.UNICODE_SNOB # covered in cli
self.escape_snob = config.ESCAPE_SNOB # covered in cli
self.links_each_paragraph = config.LINKS_EACH_PARAGRAPH
self.body_width = bodywidth # covered in cli
self.skip_internal_links = config.SKIP_INTERNAL_LINKS # covered in cli
self.inline_links = config.INLINE_LINKS # covered in cli
self.protect_links = config.PROTECT_LINKS # covered in cli
self.google_list_indent = config.GOOGLE_LIST_INDENT # covered in cli
self.ignore_links = config.IGNORE_ANCHORS # covered in cli
self.ignore_images = config.IGNORE_IMAGES # covered in cli
self.images_to_alt = config.IMAGES_TO_ALT # covered in cli
self.images_with_size = config.IMAGES_WITH_SIZE # covered in cli
self.ignore_emphasis = config.IGNORE_EMPHASIS # covered in cli
self.bypass_tables = config.BYPASS_TABLES # covered in cli
self.google_doc = False # covered in cli
self.ul_item_mark = '*' # covered in cli
self.emphasis_mark = '_' # covered in cli
self.strong_mark = '**'
self.single_line_break = config.SINGLE_LINE_BREAK # covered in cli
self.use_automatic_links = config.USE_AUTOMATIC_LINKS # covered in cli
self.hide_strikethrough = False # covered in cli
self.mark_code = config.MARK_CODE
self.wrap_links = config.WRAP_LINKS # covered in cli
self.tag_callback = None
if out is None: # pragma: no cover
self.out = self.outtextf
else: # pragma: no cover
self.out = out
# empty list to store output characters before they are "joined"
self.outtextlist = []
self.quiet = 0
self.p_p = 0 # number of newline character to print before next output
self.outcount = 0
self.start = 1
self.space = 0
self.a = []
self.astack = []
self.maybe_automatic_link = None
self.empty_link = False
self.absolute_url_matcher = re.compile(r'^[a-zA-Z+]+://')
self.acount = 0
self.list = []
self.blockquote = 0
self.pre = 0
self.startpre = 0
self.code = False
self.br_toggle = ''
self.lastWasNL = 0
self.lastWasList = False
self.style = 0
self.style_def = {}
self.tag_stack = []
self.emphasis = 0
self.drop_white_space = 0
self.inheader = False
self.abbr_title = None # current abbreviation definition
self.abbr_data = None # last inner HTML (for abbr being defined)
self.abbr_list = {} # stack of abbreviations to write later
self.baseurl = baseurl
try:
del unifiable_n[name2cp('nbsp')]
except KeyError:
pass
config.UNIFIABLE['nbsp'] = '&nbsp_place_holder;'
def feed(self, data):
data = data.replace("</' + 'script>", "</ignore>")
HTMLParser.HTMLParser.feed(self, data)
def handle(self, data):
self.feed(data)
self.feed("")
return self.optwrap(self.close())
def outtextf(self, s):
self.outtextlist.append(s)
if s:
self.lastWasNL = s[-1] == '\n'
def close(self):
HTMLParser.HTMLParser.close(self)
try:
nochr = unicode('')
except NameError:
nochr = str('')
self.pbr()
self.o('', 0, 'end')
outtext = nochr.join(self.outtextlist)
if self.unicode_snob:
try:
nbsp = unichr(name2cp('nbsp'))
except NameError:
nbsp = chr(name2cp('nbsp'))
else:
try:
nbsp = unichr(32)
except NameError:
nbsp = chr(32)
try:
outtext = outtext.replace(unicode('&nbsp_place_holder;'), nbsp)
except NameError:
outtext = outtext.replace('&nbsp_place_holder;', nbsp)
# Clear self.outtextlist to avoid memory leak of its content to
# the next handling.
self.outtextlist = []
return outtext
def handle_charref(self, c):
charref = self.charref(c)
if not self.code and not self.pre:
charref = cgi.escape(charref)
self.handle_data(charref, True)
def handle_entityref(self, c):
entityref = self.entityref(c)
if (not self.code and not self.pre
and entityref != '&nbsp_place_holder;'):
entityref = cgi.escape(entityref)
self.handle_data(entityref, True)
def handle_starttag(self, tag, attrs):
self.handle_tag(tag, attrs, 1)
def handle_endtag(self, tag):
self.handle_tag(tag, None, 0)
def previousIndex(self, attrs):
"""
:type attrs: dict
:returns: The index of certain set of attributes (of a link) in the
self.a list. If the set of attributes is not found, returns None
:rtype: int
"""
if 'href' not in attrs: # pragma: no cover
return None
i = -1
for a in self.a:
i += 1
match = 0
if ('href' in a) and a['href'] == attrs['href']:
if ('title' in a) or ('title' in attrs):
if (('title' in a) and ('title' in attrs) and
a['title'] == attrs['title']):
match = True
else:
match = True
if match:
return i
def handle_emphasis(self, start, tag_style, parent_style):
"""
Handles various text emphases
"""
tag_emphasis = google_text_emphasis(tag_style)
parent_emphasis = google_text_emphasis(parent_style)
# handle Google's text emphasis
strikethrough = 'line-through' in \
tag_emphasis and self.hide_strikethrough
bold = 'bold' in tag_emphasis and not 'bold' in parent_emphasis
italic = 'italic' in tag_emphasis and not 'italic' in parent_emphasis
fixed = google_fixed_width_font(tag_style) and not \
google_fixed_width_font(parent_style) and not self.pre
if start:
# crossed-out text must be handled before other attributes
# in order not to output qualifiers unnecessarily
if bold or italic or fixed:
self.emphasis += 1
if strikethrough:
self.quiet += 1
if italic:
self.o(self.emphasis_mark)
self.drop_white_space += 1
if bold:
self.o(self.strong_mark)
self.drop_white_space += 1
if fixed:
self.o('`')
self.drop_white_space += 1
self.code = True
else:
if bold or italic or fixed:
# there must not be whitespace before closing emphasis mark
self.emphasis -= 1
self.space = 0
if fixed:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_white_space -= 1
else:
self.o('`')
self.code = False
if bold:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_white_space -= 1
else:
self.o(self.strong_mark)
if italic:
if self.drop_white_space:
# empty emphasis, drop it
self.drop_white_space -= 1
else:
self.o(self.emphasis_mark)
# space is only allowed after *all* emphasis marks
if (bold or italic) and not self.emphasis:
self.o(" ")
if strikethrough:
self.quiet -= 1
def handle_tag(self, tag, attrs, start):
# attrs is None for endtags
if attrs is None:
attrs = {}
else:
attrs = dict(attrs)
if self.tag_callback is not None:
if self.tag_callback(self, tag, attrs, start) is True:
return
# first thing inside the anchor tag is another tag that produces some output
if (start and not self.maybe_automatic_link is None
and tag not in ['p', 'div', 'style', 'dl', 'dt']
and (tag != "img" or self.ignore_images)):
self.o("[")
self.maybe_automatic_link = None
self.empty_link = False
if self.google_doc:
# the attrs parameter is empty for a closing tag. in addition, we
# need the attributes of the parent nodes in order to get a
# complete style description for the current element. we assume
# that google docs export well formed html.
parent_style = {}
if start:
if self.tag_stack:
parent_style = self.tag_stack[-1][2]
tag_style = element_style(attrs, self.style_def, parent_style)
self.tag_stack.append((tag, attrs, tag_style))
else:
dummy, attrs, tag_style = self.tag_stack.pop() if self.tag_stack else (None, {}, {})
if self.tag_stack:
parent_style = self.tag_stack[-1][2]
if hn(tag):
self.p()
if start:
self.inheader = True
self.o(hn(tag) * "#" + ' ')
else:
self.inheader = False
return # prevent redundant emphasis marks on headers
if tag in ['p', 'div']:
if self.google_doc:
if start and google_has_height(tag_style):
self.p()
else:
self.soft_br()
else:
self.p()
if tag == "br" and start:
self.o(" \n")
if tag == "hr" and start:
self.p()
self.o("* * *")
self.p()
if tag in ["head", "style", 'script']:
if start:
self.quiet += 1
else:
self.quiet -= 1
if tag == "style":
if start:
self.style += 1
else:
self.style -= 1
if tag in ["body"]:
self.quiet = 0 # sites like 9rules.com never close <head>
if tag == "blockquote":
if start:
self.p()
self.o('> ', 0, 1)
self.start = 1
self.blockquote += 1
else:
self.blockquote -= 1
self.p()
if tag in ['em', 'i', 'u'] and not self.ignore_emphasis:
self.o(self.emphasis_mark)
if tag in ['strong', 'b'] and not self.ignore_emphasis:
self.o(self.strong_mark)
if tag in ['del', 'strike', 's']:
if start:
self.o('~~')
else:
self.o('~~')
if self.google_doc:
if not self.inheader:
# handle some font attributes, but leave headers clean
self.handle_emphasis(start, tag_style, parent_style)
if tag in ["code", "tt"] and not self.pre:
self.o('`') # TODO: `` `this` ``
self.code = not self.code
if tag == "abbr":
if start:
self.abbr_title = None
self.abbr_data = ''
if ('title' in attrs):
self.abbr_title = attrs['title']
else:
if self.abbr_title is not None:
self.abbr_list[self.abbr_data] = self.abbr_title
self.abbr_title = None
self.abbr_data = ''
if tag == "a" and not self.ignore_links:
if start:
if ('href' in attrs) and \
(attrs['href'] is not None) and \
not (self.skip_internal_links and
attrs['href'].startswith('#')):
self.astack.append(attrs)
self.maybe_automatic_link = attrs['href']
self.empty_link = True
if self.protect_links:
attrs['href'] = '<'+attrs['href']+'>'
else:
self.astack.append(None)
else:
if self.astack:
a = self.astack.pop()
if self.maybe_automatic_link and not self.empty_link:
self.maybe_automatic_link = None
elif a:
if self.empty_link:
self.o("[")
self.empty_link = False
self.maybe_automatic_link = None
if self.inline_links:
try:
title = escape_md(a['title'])
except KeyError:
self.o("](" + escape_md(urlparse.urljoin(self.baseurl, a['href'])) + ")")
else:
self.o("](" + escape_md(urlparse.urljoin(self.baseurl, a['href']))
+ ' "' + title + '" )')
else:
i = self.previousIndex(a)
if i is not None:
a = self.a[i]
else:
self.acount += 1
a['count'] = self.acount
a['outcount'] = self.outcount
self.a.append(a)
self.o("][" + str(a['count']) + "]")
if tag == "img" and start and not self.ignore_images:
if 'src' in attrs:
if not self.images_to_alt:
attrs['href'] = attrs['src']
alt = attrs.get('alt') or ''
# If we have images_with_size, write raw html including width,
# height, and alt attributes
if self.images_with_size and \
("width" in attrs or "height" in attrs):
self.o("<img src='" + attrs["src"] + "' ")
if "width" in attrs:
self.o("width='" + attrs["width"] + "' ")
if "height" in attrs:
self.o("height='" + attrs["height"] + "' ")
if alt:
self.o("alt='" + alt + "' ")
self.o("/>")
return
# If we have a link to create, output the start
if not self.maybe_automatic_link is None:
href = self.maybe_automatic_link
if self.images_to_alt and escape_md(alt) == href and \
self.absolute_url_matcher.match(href):
self.o("<" + escape_md(alt) + ">")
self.empty_link = False
return
else:
self.o("[")
self.maybe_automatic_link = None
self.empty_link = False
# If we have images_to_alt, we discard the image itself,
# considering only the alt text.
if self.images_to_alt:
self.o(escape_md(alt))
else:
self.o("![" + escape_md(alt) + "]")
if self.inline_links:
href = attrs.get('href') or ''
self.o("(" + escape_md(urlparse.urljoin(self.baseurl, href)) + ")")
else:
i = self.previousIndex(attrs)
if i is not None:
attrs = self.a[i]
else:
self.acount += 1
attrs['count'] = self.acount
attrs['outcount'] = self.outcount
self.a.append(attrs)
self.o("[" + str(attrs['count']) + "]")
if tag == 'dl' and start:
self.p()
if tag == 'dt' and not start:
self.pbr()
if tag == 'dd' and start:
self.o(' ')
if tag == 'dd' and not start:
self.pbr()
if tag in ["ol", "ul"]:
# Google Docs create sub lists as top level lists
if (not self.list) and (not self.lastWasList):
self.p()
if start:
if self.google_doc:
list_style = google_list_style(tag_style)
else:
list_style = tag
numbering_start = list_numbering_start(attrs)
self.list.append({
'name': list_style,
'num': numbering_start
})
else:
if self.list:
self.list.pop()
if (not self.google_doc) and (not self.list):
self.o('\n')
self.lastWasList = True
else:
self.lastWasList = False
if tag == 'li':
self.pbr()
if start:
if self.list:
li = self.list[-1]
else:
li = {'name': 'ul', 'num': 0}
if self.google_doc:
nest_count = self.google_nest_count(tag_style)
else:
nest_count = len(self.list)
# TODO: line up <ol><li>s > 9 correctly.
self.o(" " * nest_count)
if li['name'] == "ul":
self.o(self.ul_item_mark + " ")
elif li['name'] == "ol":
li['num'] += 1
self.o(str(li['num']) + ". ")
self.start = 1
if tag in ["table", "tr", "td", "th"]:
if self.bypass_tables:
if start:
self.soft_br()
if tag in ["td", "th"]:
if start:
self.o('<{0}>\n\n'.format(tag))
else:
self.o('\n</{0}>'.format(tag))
else:
if start:
self.o('<{0}>'.format(tag))
else:
self.o('</{0}>'.format(tag))
else:
if tag == "table" and start:
self.table_start = True
if tag in ["td", "th"] and start:
if self.split_next_td:
self.o("| ")
self.split_next_td = True
if tag == "tr" and start:
self.td_count = 0
if tag == "tr" and not start:
self.split_next_td = False
self.soft_br()
if tag == "tr" and not start and self.table_start:
# Underline table header
self.o("|".join(["---"] * self.td_count))
self.soft_br()
self.table_start = False
if tag in ["td", "th"] and start:
self.td_count += 1
if tag == "pre":
if start:
self.startpre = 1
self.pre = 1
else:
self.pre = 0
if self.mark_code:
self.out("\n[/code]")
self.p()
# TODO: Add docstring for these one letter functions
def pbr(self):
"Pretty print has a line break"
if self.p_p == 0:
self.p_p = 1
def p(self):
"Set pretty print to 1 or 2 lines"
self.p_p = 1 if self.single_line_break else 2
def soft_br(self):
"Soft breaks"
self.pbr()
self.br_toggle = ' '
def o(self, data, puredata=0, force=0):
"""
Deal with indentation and whitespace
"""
if self.abbr_data is not None:
self.abbr_data += data
if not self.quiet:
if self.google_doc:
# prevent white space immediately after 'begin emphasis'
# marks ('**' and '_')
lstripped_data = data.lstrip()
if self.drop_white_space and not (self.pre or self.code):
data = lstripped_data
if lstripped_data != '':
self.drop_white_space = 0
if puredata and not self.pre:
# This is a very dangerous call ... it could mess up
# all handling of &nbsp; when not handled properly
# (see entityref)
data = re.sub(r'\s+', r' ', data)
if data and data[0] == ' ':
self.space = 1
data = data[1:]
if not data and not force:
return
if self.startpre:
#self.out(" :") #TODO: not output when already one there
if not data.startswith("\n"): # <pre>stuff...
data = "\n" + data
if self.mark_code:
self.out("\n[code]")
self.p_p = 0
bq = (">" * self.blockquote)
if not (force and data and data[0] == ">") and self.blockquote:
bq += " "
if self.pre:
if not self.list:
bq += " "
#else: list content is already partially indented
for i in range(len(self.list)):
bq += " "
data = data.replace("\n", "\n" + bq)
if self.startpre:
self.startpre = 0
if self.list:
# use existing initial indentation
data = data.lstrip("\n")
if self.start:
self.space = 0
self.p_p = 0
self.start = 0
if force == 'end':
# It's the end.
self.p_p = 0
self.out("\n")
self.space = 0
if self.p_p:
self.out((self.br_toggle + '\n' + bq) * self.p_p)
self.space = 0
self.br_toggle = ''
if self.space:
if not self.lastWasNL:
self.out(' ')
self.space = 0
if self.a and ((self.p_p == 2 and self.links_each_paragraph)
or force == "end"):
if force == "end":
self.out("\n")
newa = []
for link in self.a:
if self.outcount > link['outcount']:
self.out(" [" + str(link['count']) + "]: " +
urlparse.urljoin(self.baseurl, link['href']))
if 'title' in link:
self.out(" (" + link['title'] + ")")
self.out("\n")
else:
newa.append(link)
# Don't need an extra line when nothing was done.
if self.a != newa:
self.out("\n")
self.a = newa
if self.abbr_list and force == "end":
for abbr, definition in self.abbr_list.items():
self.out(" *[" + abbr + "]: " + definition + "\n")
self.p_p = 0
self.out(data)
self.outcount += 1
def handle_data(self, data, entity_char=False):
if r'\/script>' in data:
self.quiet -= 1
if self.style:
self.style_def.update(dumb_css_parser(data))
if not self.maybe_automatic_link is None:
href = self.maybe_automatic_link
if (href == data and self.absolute_url_matcher.match(href)
and self.use_automatic_links):
self.o("<" + data + ">")
self.empty_link = False
return
else:
self.o("[")
self.maybe_automatic_link = None
self.empty_link = False
if not self.code and not self.pre and not entity_char:
data = escape_md_section(data, snob=self.escape_snob)
self.o(data, 1)
def unknown_decl(self, data): # pragma: no cover
# TODO: what is this doing here?
pass
def charref(self, name):
if name[0] in ['x', 'X']:
c = int(name[1:], 16)
else:
c = int(name)
if not self.unicode_snob and c in unifiable_n.keys():
return unifiable_n[c]
else:
try:
try:
return unichr(c)
except NameError: # Python3
return chr(c)
except ValueError: # invalid unicode
return ''
def entityref(self, c):
if not self.unicode_snob and c in config.UNIFIABLE.keys():
return config.UNIFIABLE[c]
else:
try:
name2cp(c)
except KeyError:
return "&" + c + ';'
else:
if c == 'nbsp':
return config.UNIFIABLE[c]
else:
try:
return unichr(name2cp(c))
except NameError: # Python3
return chr(name2cp(c))
def replaceEntities(self, s):
s = s.group(1)
if s[0] == "#":
return self.charref(s[1:])
else:
return self.entityref(s)
def unescape(self, s):
return config.RE_UNESCAPE.sub(self.replaceEntities, s)
def google_nest_count(self, style):
"""
Calculate the nesting count of google doc lists
:type style: dict
:rtype: int
"""
nest_count = 0
if 'margin-left' in style:
nest_count = int(style['margin-left'][:-2]) \
// self.google_list_indent
return nest_count
def optwrap(self, text):
"""
Wrap all paragraphs in the provided text.
:type text: str
:rtype: str
"""
if not self.body_width:
return text
assert wrap, "Requires Python 2.3."
result = ''
newlines = 0
# I cannot think of a better solution for now.
# To avoid the non-wrap behaviour for entire paras
# because of the presence of a link in it
if not self.wrap_links:
self.inline_links = False
for para in text.split("\n"):
if len(para) > 0:
if not skipwrap(para, self.wrap_links):
result += "\n".join(wrap(para, self.body_width))
if para.endswith(' '):
result += " \n"
newlines = 1
else:
result += "\n\n"
newlines = 2
else:
# Warning for the tempted!!!
# Be aware that obvious replacement of this with
# line.isspace()
# DOES NOT work! Explanations are welcome.
if not config.RE_SPACE.match(para):
result += para + "\n"
newlines = 1
else:
if newlines < 2:
result += "\n"
newlines += 1
return result
def html2text(html, baseurl='', bodywidth=None):
if bodywidth is None:
bodywidth = config.BODY_WIDTH
h = HTML2Text(baseurl=baseurl, bodywidth=bodywidth)
return h.handle(html)
def unescape(s, unicode_snob=False):
h = HTML2Text()
h.unicode_snob = unicode_snob
return h.unescape(s)
if __name__ == "__main__":
from html2text.cli import main
main()
+13
View File
@@ -0,0 +1,13 @@
import sys
if sys.version_info[0] == 2:
import htmlentitydefs
import urlparse
import HTMLParser
import urllib
else:
import urllib.parse as urlparse
import html.entities as htmlentitydefs
import html.parser as HTMLParser
import urllib.request as urllib
+123
View File
@@ -0,0 +1,123 @@
import re
# Use Unicode characters instead of their ascii psuedo-replacements
UNICODE_SNOB = 0
# Escape all special characters. Output is less readable, but avoids
# corner case formatting issues.
ESCAPE_SNOB = 0
# Put the links after each paragraph instead of at the end.
LINKS_EACH_PARAGRAPH = 0
# Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
BODY_WIDTH = 78
# Don't show internal links (href="#local-anchor") -- corresponding link
# targets won't be visible in the plain text file anyway.
SKIP_INTERNAL_LINKS = True
# Use inline, rather than reference, formatting for images and links
INLINE_LINKS = True
# Protect links from line breaks surrounding them with angle brackets (in
# addition to their square brackets)
PROTECT_LINKS = False
# WRAP_LINKS = True
WRAP_LINKS = True
# Number of pixels Google indents nested lists
GOOGLE_LIST_INDENT = 36
IGNORE_ANCHORS = False
IGNORE_IMAGES = False
IMAGES_TO_ALT = False
IMAGES_WITH_SIZE = False
IGNORE_EMPHASIS = False
MARK_CODE = False
DECODE_ERRORS = 'strict'
# Convert links with same href and text to <href> format if they are absolute links
USE_AUTOMATIC_LINKS = True
# For checking space-only lines on line 771
RE_SPACE = re.compile(r'\s\+')
RE_UNESCAPE = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
RE_ORDERED_LIST_MATCHER = re.compile(r'\d+\.\s')
RE_UNORDERED_LIST_MATCHER = re.compile(r'[-\*\+]\s')
RE_MD_CHARS_MATCHER = re.compile(r"([\\\[\]\(\)])")
RE_MD_CHARS_MATCHER_ALL = re.compile(r"([`\*_{}\[\]\(\)#!])")
RE_LINK = re.compile(r"(\[.*?\] ?\(.*?\))|(\[.*?\]:.*?)") # to find links in the text
RE_MD_DOT_MATCHER = re.compile(r"""
^ # start of line
(\s*\d+) # optional whitespace and a number
(\.) # dot
(?=\s) # lookahead assert whitespace
""", re.MULTILINE | re.VERBOSE)
RE_MD_PLUS_MATCHER = re.compile(r"""
^
(\s*)
(\+)
(?=\s)
""", flags=re.MULTILINE | re.VERBOSE)
RE_MD_DASH_MATCHER = re.compile(r"""
^
(\s*)
(-)
(?=\s|\-) # followed by whitespace (bullet list, or spaced out hr)
# or another dash (header or hr)
""", flags=re.MULTILINE | re.VERBOSE)
RE_SLASH_CHARS = r'\`*_{}[]()#+-.!'
RE_MD_BACKSLASH_MATCHER = re.compile(r'''
(\\) # match one slash
(?=[%s]) # followed by a char that requires escaping
''' % re.escape(RE_SLASH_CHARS),
flags=re.VERBOSE)
UNIFIABLE = {
'rsquo': "'",
'lsquo': "'",
'rdquo': '"',
'ldquo': '"',
'copy': '(C)',
'mdash': '--',
'nbsp': ' ',
'rarr': '->',
'larr': '<-',
'middot': '*',
'ndash': '-',
'oelig': 'oe',
'aelig': 'ae',
'agrave': 'a',
'aacute': 'a',
'acirc': 'a',
'atilde': 'a',
'auml': 'a',
'aring': 'a',
'egrave': 'e',
'eacute': 'e',
'ecirc': 'e',
'euml': 'e',
'igrave': 'i',
'iacute': 'i',
'icirc': 'i',
'iuml': 'i',
'ograve': 'o',
'oacute': 'o',
'ocirc': 'o',
'otilde': 'o',
'ouml': 'o',
'ugrave': 'u',
'uacute': 'u',
'ucirc': 'u',
'uuml': 'u',
'lrm': '',
'rlm': ''
}
BYPASS_TABLES = False
# Use a single line break after a block element rather an two line breaks.
# NOTE: Requires body width setting to be 0.
SINGLE_LINE_BREAK = False
+246
View File
@@ -0,0 +1,246 @@
import sys
from html2text import config
from html2text.compat import htmlentitydefs
def name2cp(k):
"""Return sname to codepoint"""
if k == 'apos':
return ord("'")
return htmlentitydefs.name2codepoint[k]
unifiable_n = {}
for k in config.UNIFIABLE.keys():
unifiable_n[name2cp(k)] = config.UNIFIABLE[k]
def hn(tag):
if tag[0] == 'h' and len(tag) == 2:
try:
n = int(tag[1])
if n in range(1, 10): # pragma: no branch
return n
except ValueError:
return 0
def dumb_property_dict(style):
"""
:returns: A hash of css attributes
"""
out = dict([(x.strip(), y.strip()) for x, y in
[z.split(':', 1) for z in
style.split(';') if ':' in z
]
]
)
return out
def dumb_css_parser(data):
"""
:type data: str
:returns: A hash of css selectors, each of which contains a hash of
css attributes.
:rtype: dict
"""
# remove @import sentences
data += ';'
importIndex = data.find('@import')
while importIndex != -1:
data = data[0:importIndex] + data[data.find(';', importIndex) + 1:]
importIndex = data.find('@import')
# parse the css. reverted from dictionary comprehension in order to
# support older pythons
elements = [x.split('{') for x in data.split('}') if '{' in x.strip()]
try:
elements = dict([(a.strip(), dumb_property_dict(b))
for a, b in elements])
except ValueError: # pragma: no cover
elements = {} # not that important
return elements
def element_style(attrs, style_def, parent_style):
"""
:type attrs: dict
:type style_def: dict
:type style_def: dict
:returns: A hash of the 'final' style attributes of the element
:rtype: dict
"""
style = parent_style.copy()
if 'class' in attrs:
for css_class in attrs['class'].split():
css_style = style_def.get('.' + css_class, {})
style.update(css_style)
if 'style' in attrs:
immediate_style = dumb_property_dict(attrs['style'])
style.update(immediate_style)
return style
def google_list_style(style):
"""
Finds out whether this is an ordered or unordered list
:type style: dict
:rtype: str
"""
if 'list-style-type' in style:
list_style = style['list-style-type']
if list_style in ['disc', 'circle', 'square', 'none']:
return 'ul'
return 'ol'
def google_has_height(style):
"""
Check if the style of the element has the 'height' attribute
explicitly defined
:type style: dict
:rtype: bool
"""
if 'height' in style:
return True
return False
def google_text_emphasis(style):
"""
:type style: dict
:returns: A list of all emphasis modifiers of the element
:rtype: list
"""
emphasis = []
if 'text-decoration' in style:
emphasis.append(style['text-decoration'])
if 'font-style' in style:
emphasis.append(style['font-style'])
if 'font-weight' in style:
emphasis.append(style['font-weight'])
return emphasis
def google_fixed_width_font(style):
"""
Check if the css of the current element defines a fixed width font
:type style: dict
:rtype: bool
"""
font_family = ''
if 'font-family' in style:
font_family = style['font-family']
if 'Courier New' == font_family or 'Consolas' == font_family:
return True
return False
def list_numbering_start(attrs):
"""
Extract numbering from list element attributes
:type attrs: dict
:rtype: int or None
"""
if 'start' in attrs:
try:
return int(attrs['start']) - 1
except ValueError:
pass
return 0
def skipwrap(para, wrap_links):
# If it appears to contain a link
# don't wrap
if (len(config.RE_LINK.findall(para)) > 0) and not wrap_links:
return True
# If the text begins with four spaces or one tab, it's a code block;
# don't wrap
if para[0:4] == ' ' or para[0] == '\t':
return True
# If the text begins with only two "--", possibly preceded by
# whitespace, that's an emdash; so wrap.
stripped = para.lstrip()
if stripped[0:2] == "--" and len(stripped) > 2 and stripped[2] != "-":
return False
# I'm not sure what this is for; I thought it was to detect lists,
# but there's a <br>-inside-<span> case in one of the tests that
# also depends upon it.
if stripped[0:1] == '-' or stripped[0:1] == '*':
return True
# If the text begins with a single -, *, or +, followed by a space,
# or an integer, followed by a ., followed by a space (in either
# case optionally proceeded by whitespace), it's a list; don't wrap.
if config.RE_ORDERED_LIST_MATCHER.match(stripped) or \
config.RE_UNORDERED_LIST_MATCHER.match(stripped):
return True
return False
def wrapwrite(text):
text = text.encode('utf-8')
try: # Python3
sys.stdout.buffer.write(text)
except AttributeError:
sys.stdout.write(text)
def wrap_read(): # pragma: no cover
"""
:rtype: str
"""
try:
return sys.stdin.read()
except AttributeError:
return sys.stdin.buffer.read()
def escape_md(text):
"""
Escapes markdown-sensitive characters within other markdown
constructs.
"""
return config.RE_MD_CHARS_MATCHER.sub(r"\\\1", text)
def escape_md_section(text, snob=False):
"""
Escapes markdown-sensitive characters across whole document sections.
"""
text = config.RE_MD_BACKSLASH_MATCHER.sub(r"\\\1", text)
if snob:
text = config.RE_MD_CHARS_MATCHER_ALL.sub(r"\\\1", text)
text = config.RE_MD_DOT_MATCHER.sub(r"\1\\\2", text)
text = config.RE_MD_PLUS_MATCHER.sub(r"\1\\\2", text)
text = config.RE_MD_DASH_MATCHER.sub(r"\1\\\2", text)
return text
+1 -1
View File
@@ -36,7 +36,7 @@ if __name__=="__main__":
os.chdir('../included_dependencies')
# 'a' for append
files=['gif.py','six.py','bs4','html5lib','chardet']
files=['gif.py','six.py','bs4','html5lib','chardet','html2text']
createZipFile("../"+filename,"a",
files,
exclude=exclude)
+2 -2
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.3.1",
version="2.3.5",
description='A tool for downloading fanfiction to eBook formats',
long_description=long_description,
@@ -81,7 +81,7 @@ setup(
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['beautifulsoup4','chardet','html5lib'], # html5lib requires 'six'.
install_requires=['beautifulsoup4','chardet','html5lib','html2text'], # html5lib requires 'six'.
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
+1 -11
View File
@@ -1,6 +1,6 @@
# ffd-retief-hrd fanficfare
application: fanficfare
version: 2-3-01
version: 2-3-05
runtime: python27
api_version: 1
threadsafe: true
@@ -34,13 +34,3 @@ handlers:
- url: /.*
script: main.app
#builtins:
#- datastore_admin: on
libraries:
- name: django
version: "1.2"
- name: PIL
version: "1.1.7"
+2 -2
View File
@@ -39,8 +39,8 @@
<img src="http://code.google.com/appengine/images/appengine-silver-120x30.gif"
alt="Powered by Google App Engine" />
<br/><br/>
This is a web front-end to <A href="http://code.google.com/p/fanficdownloader/">FanFicFare</a><br/>
Copyright &copy; Fanficdownloader team
This is a web front-end to <a href="https://github.com/JimmXinu/FanFicFare/">FanFicFare</a><br/>
Copyright &copy; FanFicFare team
</div>
</div>
+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-3-00.fanficfare.appspot.com">previous version
<a href="http://2-3-04a.fanficfare.appspot.com">previous version
</a> is also available for you to use if necessary.
</p>
<div id='error'>
+1 -1
View File
@@ -68,7 +68,7 @@
<img src="http://code.google.com/appengine/images/appengine-silver-120x30.gif"
alt="Powered by Google App Engine" />
<br/><br/>
This is a web front-end to <A href="http://code.google.com/p/fanficdownloader/">FanFicFare</a><br/>
This is a web front-end to <a href="https://github.com/JimmXinu/FanFicFare/">FanFicFare</a><br/>
Copyright &copy; FanFicFare team
</div>
-14
View File
@@ -30,20 +30,6 @@ import datetime
import traceback
from StringIO import StringIO
## Just to shut up the appengine warning about "You are using the
## default Django version (0.96). The default Django version will
## change in an App Engine release in the near future. Please call
## use_library() to explicitly select a Django version. For more
## information see
## http://code.google.com/appengine/docs/python/tools/libraries.html#Django"
## Note that if you are using the SDK App Engine Launcher and hit an SDK
## Console page first, you will get a django version mismatch error when you
## to go hit one of the application pages. Just change a file again, and
## make sure to hit an app page before the SDK page to clear it.
#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
#from google.appengine.dist import use_library
#use_library('django', '1.2')
from google.appengine.ext import db
from google.appengine.api import taskqueue
from google.appengine.api import users
+1 -1
View File
@@ -45,7 +45,7 @@
<img src="http://code.google.com/appengine/images/appengine-silver-120x30.gif"
alt="Powered by Google App Engine" />
<br/><br/>
This is a web front-end to <A href="http://code.google.com/p/fanficdownloader/">FanFicFare</a><br/>
This is a web front-end to <a href="https://github.com/JimmXinu/FanFicFare/">FanFicFare</a><br/>
Copyright &copy; FanFicFare team
</div>