mirror of
https://github.com/wassname/FanFicFare.git
synced 2026-09-13 12:11:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d65c334f | ||
|
|
2c5371d95e | ||
|
|
6f4660763f | ||
|
|
75f8f72266 | ||
|
|
b1d689ba3e | ||
|
|
39bb6e37f6 | ||
|
|
288f12afed | ||
|
|
2a25aef7ac | ||
|
|
085fb47b08 | ||
|
|
5fdcbab46a | ||
|
|
2d83fa8f5d | ||
|
|
4a752e05e1 | ||
|
|
f1d9760aa9 | ||
|
|
b11bdd82db | ||
|
|
6e5de8060e | ||
|
|
2c1b9456bf | ||
|
|
60439cc658 | ||
|
|
fb95e1e168 | ||
|
|
0d490d2e50 | ||
|
|
838510c011 | ||
|
|
954ad00ca6 | ||
|
|
ee462d3742 | ||
|
|
56401e6dfa | ||
|
|
6070accbf5 | ||
|
|
eebb1ee8d0 | ||
|
|
bda307ed3d | ||
|
|
6b0c519f99 | ||
|
|
6533ee0187 | ||
|
|
3cc3b32012 | ||
|
|
315536a75b | ||
|
|
1c4250c0c1 | ||
|
|
6f7e700cd2 | ||
|
|
a593bd201c | ||
|
|
37885a9039 | ||
|
|
97437aa7da | ||
|
|
ccfcd801a3 | ||
|
|
4df321c18e | ||
|
|
664fb639bd | ||
|
|
0cd71263b1 | ||
|
|
2fc1a14ac4 | ||
|
|
8772b50451 | ||
|
|
7390424f0f | ||
|
|
3a16c49c2e | ||
|
|
d8066d4bcf | ||
|
|
61fff980b5 | ||
|
|
bf704fcdc7 | ||
|
|
c9f86bd784 | ||
|
|
76faea3b4e | ||
|
|
6b3a19bb45 | ||
|
|
df7bf64845 |
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "Додати"
|
||||
|
||||
@@ -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("<<<")) # <<<
|
||||
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(">>>"))
|
||||
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)
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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+((?:&ageconsent=ok)?&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 & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
# &warning= -- actually, so do other sites. Must be an
|
||||
# eFiction book.
|
||||
|
||||
# viewstory.php?sid=1882&warning=4
|
||||
# viewstory.php?sid=1654&ageconsent=ok&warning=5
|
||||
#print data
|
||||
#m = re.search(r"'viewstory.php\?sid=1882(&warning=4)'",data)
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&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 & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
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)
|
||||
@@ -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))
|
||||
|
||||
@@ -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+((?:&ageconsent=ok)?&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 & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
@@ -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,
|
||||
|
||||
@@ -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()))))
|
||||
|
||||
@@ -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'] = ' _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(' _place_holder;'), nbsp)
|
||||
except NameError:
|
||||
outtext = outtext.replace(' _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 != ' _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 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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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 © Fanficdownloader team
|
||||
This is a web front-end to <a href="https://github.com/JimmXinu/FanFicFare/">FanFicFare</a><br/>
|
||||
Copyright © FanFicFare team
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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 © FanFicFare team
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 © FanFicFare team
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user