diff --git a/calibre-plugin/basicinihighlighter.py b/calibre-plugin/basicinihighlighter.py
index c4ea2c9..b948a56 100644
--- a/calibre-plugin/basicinihighlighter.py
+++ b/calibre-plugin/basicinihighlighter.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division,
print_function)
diff --git a/calibre-plugin/common_utils.py b/calibre-plugin/common_utils.py
index 9f135b4..d032027 100644
--- a/calibre-plugin/common_utils.py
+++ b/calibre-plugin/common_utils.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division, absolute_import,
print_function)
diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py
index 35423ee..f04c9fd 100644
--- a/calibre-plugin/config.py
+++ b/calibre-plugin/config.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division, absolute_import,
print_function)
@@ -89,7 +89,7 @@ from calibre_plugins.fanficfare_plugin.dialogs \
EditTextDialog, IniTextDialog, RejectUrlEntry)
from calibre_plugins.fanficfare_plugin.fanficfare.adapters \
- import getConfigSections
+ import getSiteSections
from calibre_plugins.fanficfare_plugin.common_utils \
import ( KeyboardConfigDialog, PrefsViewerDialog )
@@ -272,6 +272,7 @@ class ConfigWidget(QWidget):
prefs['addtolists'] = self.readinglist_tab.addtolists.isChecked()
prefs['addtoreadlists'] = self.readinglist_tab.addtoreadlists.isChecked()
prefs['addtolistsonread'] = self.readinglist_tab.addtolistsonread.isChecked()
+ prefs['autounnew'] = self.readinglist_tab.autounnew.isChecked()
# personal.ini
ini = self.personalini_tab.personalini
@@ -781,6 +782,11 @@ class ReadingListTab(QWidget):
self.addtolistsonread.setChecked(prefs['addtolistsonread'])
self.l.addWidget(self.addtolistsonread)
+ self.autounnew = QCheckBox(_('Automatically run Remove "New" Chapter Marks when marking books "Read".'),self)
+ self.autounnew.setToolTip(_('Menu option to remove from "To Read" lists will also remove "(new)" chapter marks created by personal.ini mark_new_chapters setting.'))
+ self.autounnew.setChecked(prefs['autounnew'])
+ self.l.addWidget(self.autounnew)
+
self.l.insertStretch(-1)
class CalibreCoverTab(QWidget):
@@ -928,7 +934,7 @@ class CalibreCoverTab(QWidget):
self.gc_dropdowns = {}
- sitelist = getConfigSections()
+ sitelist = getSiteSections()
sitelist.sort()
sitelist.insert(0,_("Default"))
for site in sitelist:
diff --git a/calibre-plugin/dialogs.py b/calibre-plugin/dialogs.py
index 7735774..3b35821 100644
--- a/calibre-plugin/dialogs.py
+++ b/calibre-plugin/dialogs.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division,
print_function)
diff --git a/calibre-plugin/fff_plugin.py b/calibre-plugin/fff_plugin.py
index e785664..37ddde3 100644
--- a/calibre-plugin/fff_plugin.py
+++ b/calibre-plugin/fff_plugin.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division, absolute_import,
print_function)
@@ -66,7 +66,8 @@ from calibre_plugins.fanficfare_plugin.fanficfare import (
adapters, exceptions)
from calibre_plugins.fanficfare_plugin.fanficfare.epubutils import (
- get_dcsource, get_dcsource_chaptercount, get_story_url_from_html)
+ get_dcsource, get_dcsource_chaptercount, get_story_url_from_html,
+ reset_orig_chapters_epub)
from calibre_plugins.fanficfare_plugin.fanficfare.geturls import (
get_urls_from_page, get_urls_from_html,get_urls_from_text,
@@ -335,6 +336,12 @@ class FanFicFarePlugin(InterfaceAction):
image='minusminus.png',
triggered=partial(self.update_lists,add=False))
+ self.menu.addSeparator()
+ self.get_list_action = self.create_menu_item_ex(self.menu, _('Remove "New" Chapter Marks from Selected books'),
+ unique_name='Remove "(new)" chapter marks created by personal.ini mark_new_chapters setting.',
+ image='edit-undo.png',
+ triggered=self.unnew_books)
+
self.menu.addSeparator()
self.get_list_action = self.create_menu_item_ex(self.menu, _('Get Story URLs from Selected Books'),
unique_name='Get URLs from Selected Books',
@@ -415,6 +422,8 @@ class FanFicFarePlugin(InterfaceAction):
return
self.update_reading_lists(self.gui.library_view.get_selected_ids(),add)
+ if not add and prefs['autounnew']:
+ self.unnew_books()
def get_urls_from_imap_menu(self):
@@ -555,6 +564,70 @@ class FanFicFarePlugin(InterfaceAction):
show=True,
show_copy_button=False)
+ def unnew_books(self):
+ '''Get list of URLs from existing books.'''
+ if not self.is_library_view():
+ 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)
+ return
+
+ book_list = map( partial(self.make_book_id_only),
+ self.gui.library_view.get_selected_ids() )
+
+ tdir = PersistentTemporaryDirectory(prefix='fanficfare_')
+ LoopProgressDialog(self.gui,
+ book_list,
+ partial(self.get_unnew_books_loop, db=self.gui.current_db, tdir=tdir),
+ partial(self.get_unnew_books_finish, tdir=tdir),
+ init_label=_("UnNewing books..."),
+ win_title=_("UnNew Books"),
+ status_prefix=_("Books UnNewed"))
+
+ def get_unnew_books_loop(self,book,db=None,tdir=None):
+
+ if book['calibre_id'] and db.has_format(book['calibre_id'],'EPUB',index_is_id=True):
+ tmp = PersistentTemporaryFile(prefix='%s-'%book['calibre_id'],
+ 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)
+ book['changed']=reset_orig_chapters_epub(tmp,unnewtmp)
+ if book['changed']:
+ db.add_format_with_hooks(book['calibre_id'],
+ 'EPUB',
+ unnewtmp,
+ index_is_id=True)
+ if prefs['deleteotherforms']:
+ fmts = db.formats(book['calibre_id'], index_is_id=True).split(',')
+ for fmt in fmts:
+ if fmt.lower() != formmapping['epub'].lower():
+ logger.debug("deleteotherforms remove f:"+fmt)
+ db.remove_format(book['calibre_id'], fmt, index_is_id=True)#, notify=False
+ elif prefs['autoconvert']:
+ ## 'Convert Book'.auto_convert_auto_add doesn't convert if
+ ## the format is already there.
+ fmt = calibre_prefs['output_format']
+ # delete if there, but not if the format we just made.
+ 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']:
+ changed_ids = [ x['calibre_id'] for x in book_list if x['changed'] ]
+ if changed_ids:
+ self.gui.status_bar.show_message(_('Starting auto conversion of %d books.')%(len(changed_ids)), 3000)
+ self.gui.iactions['Convert Books'].auto_convert_auto_add(changed_ids)
+
def reject_list_urls(self):
if self.is_library_view():
book_list = map( partial(self.make_book_id_only),
diff --git a/calibre-plugin/fff_util.py b/calibre-plugin/fff_util.py
index b1e8a4d..be35131 100644
--- a/calibre-plugin/fff_util.py
+++ b/calibre-plugin/fff_util.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division, absolute_import,
print_function)
@@ -23,12 +23,12 @@ def get_fff_personalini():
def get_fff_config(url,fileform="epub",personalini=None):
if not personalini:
personalini = get_fff_personalini()
- site='unknown'
+ sections=['unknown']
try:
- site = adapters.getConfigSectionFor(url)
+ sections = adapters.getConfigSectionsFor(url)
except Exception as e:
- logger.debug("Failed trying to get ini config for url(%s): %s, using section [%s] instead"%(url,e,site))
- configuration = Configuration(site,fileform)
+ logger.debug("Failed trying to get ini config for url(%s): %s, using section %s instead"%(url,e,sections))
+ configuration = Configuration(sections,fileform)
configuration.readfp(StringIO(get_resources("plugin-defaults.ini")))
configuration.readfp(StringIO(personalini))
diff --git a/calibre-plugin/inihighlighter.py b/calibre-plugin/inihighlighter.py
index 2f34198..7348d80 100644
--- a/calibre-plugin/inihighlighter.py
+++ b/calibre-plugin/inihighlighter.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division,
print_function)
diff --git a/calibre-plugin/jobs.py b/calibre-plugin/jobs.py
index 223d2ba..de8e017 100644
--- a/calibre-plugin/jobs.py
+++ b/calibre-plugin/jobs.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+# -*- coding: utf-8 -*-
+
from __future__ import (unicode_literals, division, absolute_import,
print_function)
@@ -192,7 +192,9 @@ def do_download_for_worker(book,options,notification=lambda x,y:x):
adapter.oldimgs,
adapter.oldcover,
adapter.calibrebookmark,
- adapter.logfile) = get_update_data(book['epub_for_update'])[0:7]
+ adapter.logfile,
+ adapter.oldchaptersmap,
+ adapter.oldchaptersdata) = get_update_data(book['epub_for_update'])[0:9]
# dup handling from fff_plugin needed for anthology updates.
if options['collision'] == UPDATE:
diff --git a/calibre-plugin/plugin-defaults.ini b/calibre-plugin/plugin-defaults.ini
index 94cd796..a545e98 100644
--- a/calibre-plugin/plugin-defaults.ini
+++ b/calibre-plugin/plugin-defaults.ini
@@ -293,12 +293,31 @@ chapter_title_strip_pattern:^[0-9]+[\.: -]+
## etc
#chapter_title_strip_pattern:^([0-9]+[\.: -]+)?(Chapter *[0-9]+[\.:, -]*)?
-## Uses a python template substitution. The ${index} is the 'chapter'
-## number and ${title} is the chapter title, after applying
-## chapter_title_strip_pattern. Those are the only variables available.
-## "The Beginning" => "1. The Beginning"
+## If true, when updating an epub that already has old chapters, new
+## chapters will be marked in the TOC and chapter header by using
+## chapter_title_new_pattern and chapter_title_addnew_pattern to set the chapter.
+mark_new_chapters:false
+
+## chapter title patterns use python template substitution. The
+## ${index} is the 'chapter' number and ${title} is the chapter title,
+## after applying chapter_title_strip_pattern. Those are the only
+## variables available.
+
+## The basic pattern used when not using add_chapter_numbers or
+## mark_new_chapters
+chapter_title_def_pattern:${title}
+
+## Pattern used with add_chapter_numbers, but not mark_new_chapters
chapter_title_add_pattern:${index}. ${title}
+## Pattern used with mark_new_chapters, but not add_chapter_numbers
+## (new) is just text and can be changed.
+chapter_title_new_pattern:(new) ${title}
+
+## Pattern used with add_chapter_numbers and mark_new_chapters
+## (new) is just text and can be changed.
+chapter_title_addnew_pattern:${index}. (new) ${title}
+
## Uses a python template substitution. The ${title} is the default
## title of a new anthology,
This sets what that option will default to."
msgstr ""
-#: config.py:390
+#: config.py:391
msgid "Default Output &Format:"
msgstr ""
-#: config.py:405
+#: config.py:406
msgid "On each download, FanFicFare offers an option of what happens if that story already exists.
This sets what that option will default to."
msgstr ""
-#: config.py:407
+#: config.py:408
msgid "Default If Story Already Exists?"
msgstr ""
-#: config.py:422
+#: config.py:423
msgid "Default Update Calibre &Metadata?"
msgstr ""
-#: config.py:423
+#: config.py:424
msgid "On each download, FanFicFare offers an option to update Calibre's metadata (title, author, URL, tags, custom columns, etc) from the web site.
This sets whether that will default to on or off.
Columns set to 'New Only' in the column tabs will only be set for new books."
msgstr ""
-#: config.py:427
+#: config.py:428
msgid "Default Update EPUB Cover when Updating EPUB?"
msgstr ""
-#: config.py:428
+#: config.py:429
msgid "On each download, FanFicFare offers an option to update the book cover image inside the EPUB from the web site when the EPUB is updated.
This sets whether that will default to on or off."
msgstr ""
-#: config.py:433
+#: config.py:434
msgid "Updating Calibre Options"
msgstr ""
-#: config.py:437
+#: config.py:438
msgid "Delete other existing formats?"
msgstr ""
-#: config.py:438
+#: config.py:439
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 ""
-#: config.py:442
+#: config.py:443
msgid "Keep Existing Tags when Updating Metadata?"
msgstr ""
-#: config.py:443
+#: config.py:444
msgid ""
"Existing tags will be kept and any new tags added.\n"
"%(cmplt)s and %(inprog)s tags will be still be updated, if known.\n"
@@ -169,336 +169,344 @@ msgid ""
"(If Tags is set to 'New Only' in the Standard Columns tab, this has no effect.)"
msgstr ""
-#: config.py:447
+#: config.py:448
msgid "Force Author into Author Sort?"
msgstr ""
-#: config.py:448
+#: config.py:449
msgid ""
"If checked, the author(s) as given will be used for the Author Sort, too.\n"
"If not checked, calibre will apply it's built in algorithm which makes 'Bob Smith' sort as 'Smith, Bob', etc."
msgstr ""
-#: config.py:452
+#: config.py:453
msgid "Force Title into Title Sort?"
msgstr ""
-#: config.py:453
+#: config.py:454
msgid ""
"If checked, the title as given will be used for the Title Sort, too.\n"
"If not checked, calibre will apply it's built in algorithm which makes 'The Title' sort as 'Title, The', etc."
msgstr ""
-#: config.py:457
+#: config.py:458
msgid "Check for existing Series Anthology books?"
msgstr ""
-#: config.py:458
+#: config.py:459
msgid ""
"Check for existings Series Anthology books using each new story's series URL before downloading.\n"
"Offer to skip downloading if a Series Anthology is found."
msgstr ""
-#: config.py:462
+#: config.py:463
msgid "Check for changed Story URL?"
msgstr ""
-#: config.py:463
+#: config.py:464
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 ""
-#: config.py:467
+#: config.py:468
msgid "Search EPUB text for Story URL?"
msgstr ""
-#: config.py:468
+#: config.py:469
msgid ""
"Look for first valid story URL inside EPUB text if not found in metadata.\n"
"Somewhat risky, could find wrong URL depending on EPUB content.\n"
"Also finds and corrects bad ffnet URLs from ficsaver.com files."
msgstr ""
-#: config.py:472
+#: config.py:473
msgid "Mark added/updated books when finished?"
msgstr ""
-#: config.py:473
+#: config.py:474
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 ""
-#: config.py:477
+#: config.py:478
msgid "Show Marked books when finished?"
msgstr ""
-#: config.py:478
+#: config.py:479
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 ""
-#: config.py:482
+#: config.py:483
msgid "Smarten Punctuation (EPUB only)"
msgstr ""
-#: config.py:483
+#: config.py:484
msgid "Run Smarten Punctuation from Calibre's Polish Book feature on each EPUB download and update."
msgstr ""
-#: config.py:487
+#: config.py:488
msgid "Automatically Convert new/update books?"
msgstr ""
-#: config.py:488
+#: config.py:489
msgid ""
"Automatically call calibre's Convert for new/update books.\n"
"Converts to the current output format as chosen in calibre's\n"
"Preferences->Behavior settings."
msgstr ""
-#: config.py:492
+#: config.py:493
msgid "GUI Options"
msgstr ""
-#: config.py:496
+#: config.py:497
msgid "Take URLs from Clipboard?"
msgstr ""
-#: config.py:497
+#: config.py:498
msgid "Prefill URLs from valid URLs in Clipboard when Adding New."
msgstr ""
-#: config.py:501
+#: config.py:502
msgid "Default to Update when books selected?"
msgstr ""
-#: config.py:502
+#: config.py:503
msgid ""
"The top FanFicFare plugin button will start Update if\n"
"books are selected. If unchecked, it will always bring up 'Add New'."
msgstr ""
-#: config.py:506
+#: config.py:507
msgid "Keep 'Add New from URL(s)' dialog on top?"
msgstr ""
-#: config.py:507
+#: config.py:508
msgid ""
"Instructs the OS and Window Manager to keep the 'Add New from URL(s)'\n"
"dialog on top of all other windows. Useful for dragging URLs onto it."
msgstr ""
-#: config.py:511
+#: config.py:512
msgid "Show estimated time left?"
msgstr ""
-#: config.py:512
+#: config.py:513
msgid "When a Progress Bar is shown, show a rough estimate of the time left."
msgstr ""
-#: config.py:516
+#: config.py:517
msgid "Misc Options"
msgstr ""
-#: config.py:520
+#: config.py:521
msgid "Inject calibre Series when none found?"
msgstr ""
-#: config.py:521
+#: config.py:522
msgid ""
"If no series is found, inject the calibre series (if there is one) so \n"
"it appears on the FanFicFare title page(not cover)."
msgstr ""
-#: config.py:525
+#: config.py:526
msgid "Search by Title/Author(s) for If Story Already Exists?"
msgstr ""
-#: config.py:526
+#: config.py:527
msgid "When checking If Story Already Exists FanFicFare will first match by URL Identifier. But if not found, it can also search existing books by Title and Author(s)."
msgstr ""
-#: config.py:530
+#: config.py:531
msgid "Reject List"
msgstr ""
-#: config.py:534
+#: config.py:535
msgid "Edit Reject URL List"
msgstr ""
-#: config.py:535
+#: config.py:536
msgid "Edit list of URLs FanFicFare will automatically Reject."
msgstr ""
-#: config.py:539 config.py:610
+#: config.py:540 config.py:611
msgid "Add Reject URLs"
msgstr ""
-#: config.py:540
+#: config.py:541
msgid "Add additional URLs to Reject as text."
msgstr ""
-#: config.py:544
+#: config.py:545
msgid "Edit Reject Reasons List"
msgstr ""
-#: config.py:545 config.py:600
+#: config.py:546 config.py:601
msgid "Customize the Reasons presented when Rejecting URLs"
msgstr ""
-#: config.py:549
+#: config.py:550
msgid "Reject Without Confirmation?"
msgstr ""
-#: config.py:550
+#: config.py:551
msgid "Always reject URLs on the Reject List without stopping and asking."
msgstr ""
-#: config.py:584
+#: config.py:585
msgid "Edit Reject URLs List"
msgstr ""
-#: config.py:598
+#: config.py:599
msgid "Reject Reasons"
msgstr ""
-#: config.py:599
+#: config.py:600
msgid "Customize Reject List Reasons"
msgstr ""
-#: config.py:608
+#: config.py:609
msgid "Reason why I rejected it"
msgstr ""
-#: config.py:608
+#: config.py:609
msgid "Title by Author"
msgstr ""
-#: config.py:611
+#: config.py:612
msgid "Add Reject URLs. Use: http://...,note or http://...,title by author - note
Invalid story URLs will be ignored."
msgstr ""
-#: config.py:612
+#: config.py:613
msgid ""
"One URL per line:\n"
"http://...,note\n"
"http://...,title by author - note"
msgstr ""
-#: config.py:614 dialogs.py:1094
+#: config.py:615 dialogs.py:1094
msgid "Add this reason to all URLs added:"
msgstr ""
-#: config.py:630
+#: config.py:631
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 ""
-#: config.py:635
+#: config.py:636
msgid "FanFicFare now includes find, color coding, and error checking for personal.ini editing. Red generally indicates errors."
msgstr ""
-#: config.py:654 config.py:695 config.py:696
+#: config.py:655 config.py:696 config.py:697
msgid "Edit personal.ini"
msgstr ""
-#: config.py:659
+#: config.py:660
msgid "Changes will only be saved if you click 'OK' to leave Customize FanFicFare."
msgstr ""
-#: config.py:663
+#: config.py:664
msgid "View Defaults"
msgstr ""
-#: config.py:664
+#: config.py:665
msgid ""
"View all of the plugin's configurable settings\n"
"and their default settings."
msgstr ""
-#: config.py:668
+#: config.py:669
msgid "Pass Calibre Columns into FanFicFare on Update/Overwrite"
msgstr ""
-#: config.py:669
+#: config.py:670
msgid "If checked, when updating/overwriting an existing book, FanFicFare will have the Calibre Columns available to use in replace_metadata, title_page, etc.
Click the button below to see the Calibre Column namess."
msgstr ""
-#: config.py:673
+#: config.py:674
msgid "Show Calibre Column Names"
msgstr ""
-#: config.py:674
+#: config.py:675
msgid "FanFicFare can pass the Calibre Columns into the download/update process.
This will show you the columns available by name."
msgstr ""
-#: config.py:685
+#: config.py:686
msgid "Plugin Defaults"
msgstr ""
-#: config.py:686
+#: config.py:687
msgid "Plugin Defaults (%s) (Read-Only)"
msgstr ""
-#: config.py:719
+#: config.py:720
msgid "Calibre Column Entry Names"
msgstr ""
-#: config.py:720
+#: config.py:721
msgid "Label (entry_name)"
msgstr ""
-#: config.py:740
+#: config.py:741
msgid "These settings provide integration with the %(rl)s Plugin. %(rl)s can automatically send to devices and change custom columns. You have to create and configure the lists in %(rl)s to be useful."
msgstr ""
-#: config.py:745
+#: config.py:746
msgid "Add new/updated stories to \"Send to Device\" Reading List(s)."
msgstr ""
-#: config.py:746
+#: config.py:747
msgid "Automatically add new/updated stories to these lists in the %(rl)s plugin."
msgstr ""
-#: config.py:751
+#: config.py:752
msgid "\"Send to Device\" Reading Lists"
msgstr ""
-#: config.py:752 config.py:755 config.py:769 config.py:772
+#: config.py:753 config.py:756 config.py:770 config.py:773
msgid "When enabled, new/updated stories will be automatically added to these lists."
msgstr ""
-#: config.py:762
+#: config.py:763
msgid "Add new/updated stories to \"To Read\" Reading List(s)."
msgstr ""
-#: config.py:763
+#: config.py:764
msgid ""
"Automatically add new/updated stories to these lists in the %(rl)s plugin.\n"
"Also offers menu option to remove stories from the \"To Read\" lists."
msgstr ""
-#: config.py:768
+#: config.py:769
msgid "\"To Read\" Reading Lists"
msgstr ""
-#: config.py:779
+#: config.py:780
msgid "Add stories back to \"Send to Device\" Reading List(s) when marked \"Read\"."
msgstr ""
-#: config.py:780
+#: config.py:781
msgid "Menu option to remove from \"To Read\" lists will also add stories back to \"Send to Device\" Reading List(s)"
msgstr ""
-#: config.py:808
+#: config.py:785
+msgid "Automatically run Remove \"New\" Chapter Marks when marking books \"Read\"."
+msgstr ""
+
+#: config.py:786
+msgid "Menu option to remove from \"To Read\" lists will also remove \"(new)\" chapter marks created by personal.ini mark_new_chapters setting."
+msgstr ""
+
+#: config.py:814
msgid "The Calibre cover image for a downloaded book can come from the story site(if EPUB and images are enabled), or from either Calibre's built-in random cover generator or the %(gc)s plugin."
msgstr ""
-#: config.py:816
+#: config.py:822
msgid ""
"Update Calibre book cover image from EPUB when Calibre metadata is updated.\n"
"Doesn't go looking for new images on 'Update Calibre Metadata Only'.\n"
@@ -506,425 +514,425 @@ msgid ""
"This comes before Generate Cover so %(gc)s(Plugin) use the image if configured to."
msgstr ""
-#: config.py:821
+#: config.py:827
msgid "Update Calibre Cover (from EPUB):"
msgstr ""
-#: config.py:839
+#: config.py:845
msgid "Generate a Calibre book cover image when Calibre metadata is updated.
Defaults to 'Yes, Always' for backward compatibility and because %(gc)s(Plugin) will only run if configured for Default or site."
msgstr ""
-#: config.py:843
+#: config.py:849
msgid "Generate Calibre Cover:"
msgstr ""
-#: config.py:870
+#: config.py:876
msgid "Plugin %(gc)s"
msgstr ""
-#: config.py:871
+#: config.py:877
msgid "Use plugin to create covers. Additional settings are below."
msgstr ""
-#: config.py:878
+#: config.py:884
msgid "Calibre Generate Cover"
msgstr ""
-#: config.py:879
+#: config.py:885
msgid "Call Calibre's Edit Metadata Generate cover feature to create a random cover each time a story is downloaded or updated.
Right click or long click the 'Generate cover' button in Calibre's Edit Metadata to customize."
msgstr ""
-#: config.py:893
+#: config.py:899
msgid "Generate Covers Only for New Books"
msgstr ""
-#: config.py:894
+#: config.py:900
msgid "Default is to generate a cover any time the calibre metadata is updated.
Used for both Calibre and Plugin generated covers."
msgstr ""
-#: config.py:900
+#: config.py:906
msgid "Inject/update the cover inside EPUB"
msgstr ""
-#: config.py:901
+#: config.py:907
msgid "Calibre's Polish feature will be used to inject or update the generated cover into the EPUB ebook file.
Used for both Calibre and Plugin generated covers."
msgstr ""
-#: config.py:907
+#: config.py:913
msgid "%(gc)s(Plugin) Settings"
msgstr ""
-#: config.py:915
+#: config.py:921
msgid "The %(gc)s plugin can create cover images for books using various metadata (including existing cover image). If you have %(gc)s installed, FanFicFare can run %(gc)s on new downloads and metadata updates. Pick a %(gc)s setting by site and/or one to use by Default."
msgstr ""
-#: config.py:933 config.py:937 config.py:950
+#: config.py:939 config.py:943 config.py:956
msgid "Default"
msgstr ""
-#: config.py:938
+#: config.py:944
msgid "On Metadata update, run %(gc)s with this setting, if there isn't a more specific setting below."
msgstr ""
-#: config.py:941
+#: config.py:947
msgid "On Metadata update, run %(gc)s with this setting for %(site)s stories."
msgstr ""
-#: config.py:964
+#: config.py:970
msgid "Allow %(gcset)s from %(pini)s to override"
msgstr ""
-#: config.py:965
+#: config.py:971
msgid "The %(pini)s parameter %(gcset)s allows you to choose a %(gc)s setting based on metadata rather than site, but it's much more complex.
%(gcset)s is ignored when this is off."
msgstr ""
-#: config.py:1003
+#: config.py:1009
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 ""
-#: config.py:1008
+#: config.py:1014
msgid "If any of the settings below are checked, when stories are added or updated, the %(cp)s Plugin will be called to update the checked statistics."
msgstr ""
-#: config.py:1014
+#: config.py:1020
msgid "Which column and algorithm to use are configured in %(cp)s."
msgstr ""
-#: config.py:1024
+#: config.py:1030
msgid "Will overwrite word count from FanFicFare metadata if set to update the same custom column."
msgstr ""
-#: config.py:1029
+#: config.py:1035
msgid "Only run Count Page's Word Count if checked and FanFicFare metadata doesn't already have a word count. If this is used with one of the other Page Counts, the Page Count plugin will be called twice."
msgstr ""
-#: config.py:1065
+#: config.py:1071
msgid "These controls aren't plugin settings as such, but convenience buttons for setting Keyboard shortcuts and getting all the FanFicFare confirmation dialogs back again."
msgstr ""
-#: config.py:1070
+#: config.py:1076
msgid "Keyboard shortcuts..."
msgstr ""
-#: config.py:1071
+#: config.py:1077
msgid "Edit the keyboard shortcuts associated with this plugin"
msgstr ""
-#: config.py:1075
+#: config.py:1081
msgid "Reset disabled &confirmation dialogs"
msgstr ""
-#: config.py:1076
+#: config.py:1082
msgid "Reset all show me again dialogs for the FanFicFare plugin"
msgstr ""
-#: config.py:1080
+#: config.py:1086
msgid "&View library preferences..."
msgstr ""
-#: config.py:1081
+#: config.py:1087
msgid "View data stored in the library database for this plugin"
msgstr ""
-#: config.py:1092
+#: config.py:1098
msgid "Done"
msgstr ""
-#: config.py:1093
+#: config.py:1099
msgid "Confirmation dialogs have all been reset"
msgstr ""
-#: config.py:1141
+#: config.py:1147
msgid "Category"
msgstr ""
-#: config.py:1142
+#: config.py:1148
msgid "Genre"
msgstr ""
-#: config.py:1143
+#: config.py:1149
msgid "Language"
msgstr ""
-#: config.py:1144 fff_plugin.py:1303 fff_plugin.py:1501 fff_plugin.py:1531
+#: config.py:1150 fff_plugin.py:1376 fff_plugin.py:1574 fff_plugin.py:1604
msgid "Status"
msgstr ""
-#: config.py:1145
+#: config.py:1151
msgid "Status:%(cmplt)s"
msgstr ""
-#: config.py:1146
+#: config.py:1152
msgid "Status:%(inprog)s"
msgstr ""
-#: config.py:1147 config.py:1295
+#: config.py:1153 config.py:1301
msgid "Series"
msgstr ""
-#: config.py:1148
+#: config.py:1154
msgid "Characters"
msgstr ""
-#: config.py:1149
+#: config.py:1155
msgid "Relationships"
msgstr ""
-#: config.py:1150
+#: config.py:1156
msgid "Published"
msgstr ""
-#: config.py:1151 fff_plugin.py:1614 fff_plugin.py:1633
+#: config.py:1157 fff_plugin.py:1687 fff_plugin.py:1706
msgid "Updated"
msgstr ""
-#: config.py:1152
+#: config.py:1158
msgid "Created"
msgstr ""
-#: config.py:1153
+#: config.py:1159
msgid "Rating"
msgstr ""
-#: config.py:1154
+#: config.py:1160
msgid "Warnings"
msgstr ""
-#: config.py:1155
+#: config.py:1161
msgid "Chapters"
msgstr ""
-#: config.py:1156
+#: config.py:1162
msgid "Words"
msgstr ""
-#: config.py:1157
+#: config.py:1163
msgid "Site"
msgstr ""
-#: config.py:1158
+#: config.py:1164
msgid "Story ID"
msgstr ""
-#: config.py:1159
+#: config.py:1165
msgid "Author ID"
msgstr ""
-#: config.py:1160
+#: config.py:1166
msgid "Extra Tags"
msgstr ""
-#: config.py:1161 config.py:1287 dialogs.py:885 dialogs.py:981
-#: fff_plugin.py:1303 fff_plugin.py:1501 fff_plugin.py:1531
+#: config.py:1167 config.py:1293 dialogs.py:885 dialogs.py:981
+#: fff_plugin.py:1376 fff_plugin.py:1574 fff_plugin.py:1604
msgid "Title"
msgstr ""
-#: config.py:1162
+#: config.py:1168
msgid "Story URL"
msgstr ""
-#: config.py:1163
+#: config.py:1169
msgid "Description"
msgstr ""
-#: config.py:1164 dialogs.py:885 dialogs.py:981 fff_plugin.py:1303
-#: fff_plugin.py:1501 fff_plugin.py:1531
+#: config.py:1170 dialogs.py:885 dialogs.py:981 fff_plugin.py:1376
+#: fff_plugin.py:1574 fff_plugin.py:1604
msgid "Author"
msgstr ""
-#: config.py:1165
+#: config.py:1171
msgid "Author URL"
msgstr ""
-#: config.py:1166
+#: config.py:1172
msgid "File Format"
msgstr ""
-#: config.py:1167
+#: config.py:1173
msgid "File Extension"
msgstr ""
-#: config.py:1168
+#: config.py:1174
msgid "Site Abbrev"
msgstr ""
-#: config.py:1169
+#: config.py:1175
msgid "FanFicFare Version"
msgstr ""
-#: config.py:1184
+#: config.py:1190
msgid "If you have custom columns defined, they will be listed below. Choose a metadata value type to fill your columns automatically."
msgstr ""
-#: config.py:1209
+#: config.py:1215
msgid "Update this %s column(%s) with..."
msgstr ""
-#: config.py:1219
+#: config.py:1225
msgid "Values that aren't valid for this enumeration column will be ignored."
msgstr ""
-#: config.py:1219 config.py:1221
+#: config.py:1225 config.py:1227
msgid "Metadata values valid for this type of column."
msgstr ""
-#: config.py:1224 config.py:1314
+#: config.py:1230 config.py:1320
msgid "New Only"
msgstr ""
-#: config.py:1225
+#: config.py:1231
msgid ""
"Write to %s(%s) only for new\n"
"books, not updates to existing books."
msgstr ""
-#: config.py:1236
+#: config.py:1242
msgid "Allow %(ccset)s from %(pini)s to override"
msgstr ""
-#: config.py:1237
+#: config.py:1243
msgid "The %(pini)s parameter %(ccset)s allows you to set custom columns to site specific values that aren't common to all sites.
%(ccset)s is ignored when this is off."
msgstr ""
-#: config.py:1241
+#: config.py:1247
msgid "Special column:"
msgstr ""
-#: config.py:1246
+#: config.py:1252
msgid "Update/Overwrite Error Column:"
msgstr ""
-#: config.py:1247
+#: config.py:1253
msgid ""
"When an update or overwrite of an existing story fails, record the reason in this column.\n"
"(Text and Long Text columns only.)"
msgstr ""
-#: config.py:1261
+#: config.py:1267
msgid "Saved Metadata Column:"
msgstr ""
-#: config.py:1262
+#: config.py:1268
msgid "If set, FanFicFare will save a copy of all its metadata in this column when the book is downloaded or updated.
The metadata from this column can later be used to update custom columns without having to request the metadata from the server again.
(Long Text columns only.)"
msgstr ""
-#: config.py:1288
+#: config.py:1294
msgid "Author(s)"
msgstr ""
-#: config.py:1289
+#: config.py:1295
msgid "Publisher"
msgstr ""
-#: config.py:1290
+#: config.py:1296
msgid "Tags"
msgstr ""
-#: config.py:1291
+#: config.py:1297
msgid "Languages"
msgstr ""
-#: config.py:1292
+#: config.py:1298
msgid "Published Date"
msgstr ""
-#: config.py:1293
+#: config.py:1299
msgid "Date"
msgstr ""
-#: config.py:1294
+#: config.py:1300
msgid "Comments"
msgstr ""
-#: config.py:1296
+#: config.py:1302
msgid "Ids(url id only)"
msgstr ""
-#: config.py:1301
+#: config.py:1307
msgid "The standard calibre metadata columns are listed below. You may choose whether FanFicFare will fill each column automatically on updates or only for new books."
msgstr ""
-#: config.py:1315
+#: config.py:1321
msgid ""
"Write to %s only for new\n"
"books, not updates to existing books."
msgstr ""
-#: config.py:1324
+#: config.py:1330
msgid "Other Standard Column Options"
msgstr ""
-#: config.py:1329
+#: config.py:1335
msgid "Set Calibre Author URL"
msgstr ""
-#: config.py:1330
+#: config.py:1336
msgid "Set Calibre Author URL to Author's URL on story site."
msgstr ""
-#: config.py:1347
+#: config.py:1353
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 ""
-#: config.py:1352
+#: config.py:1358
msgid "IMAP Server Name"
msgstr ""
-#: config.py:1353
+#: config.py:1359
msgid "Name of IMAP server--must allow IMAP4 with SSL. Eg: imap.gmail.com"
msgstr ""
-#: config.py:1362
+#: config.py:1368
msgid "IMAP User Name"
msgstr ""
-#: config.py:1363
+#: config.py:1369
msgid ""
"Name of IMAP user. Eg: yourname@gmail.com\n"
"Note that Gmail accounts need to have IMAP enabled in Gmail Settings first."
msgstr ""
-#: config.py:1372
+#: config.py:1378
msgid "IMAP User Password"
msgstr ""
-#: config.py:1373
+#: config.py:1379
msgid "IMAP password. If left empty, FanFicFare will ask you for your password when you use the feature."
msgstr ""
-#: config.py:1383
+#: config.py:1389
msgid "Remember Password for Session (when not saved above)"
msgstr ""
-#: config.py:1384
+#: config.py:1390
msgid "If checked, and no password is entered above, FanFicFare will remember your password until you close calibre or change Libraries."
msgstr ""
-#: config.py:1389
+#: config.py:1395
msgid "IMAP Folder Name"
msgstr ""
-#: config.py:1390
+#: config.py:1396
msgid "Name of IMAP folder to search for new emails. The folder (or label) has to already exist. Use INBOX for your default inbox."
msgstr ""
-#: config.py:1399
+#: config.py:1405
msgid "Mark Emails Read"
msgstr ""
-#: config.py:1400
+#: config.py:1406
msgid "If checked, emails will be marked as having been read if they contain any story URLs."
msgstr ""
-#: config.py:1405
+#: config.py:1411
msgid "Discard URLs on Reject List"
msgstr ""
-#: config.py:1406
+#: config.py:1412
msgid "If checked, FanFicFare will silently discard story URLs from emails that are on your Reject URL List.
Otherwise they will appear and you will see the normal Reject URL dialog.
The Emails will still be marked Read if configured to."
msgstr ""
-#: config.py:1411
+#: config.py:1417
msgid "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.
Use this feature at your own risk. "
msgstr ""
@@ -1129,7 +1137,7 @@ msgstr ""
msgid "less than 1 second"
msgstr ""
-#: dialogs.py:713 fff_plugin.py:358 fff_plugin.py:361
+#: dialogs.py:713 fff_plugin.py:365 fff_plugin.py:368
msgid "About FanFicFare"
msgstr ""
@@ -1145,7 +1153,7 @@ msgstr ""
msgid "What sort of update to perform. May set default from plugin configuration."
msgstr ""
-#: dialogs.py:885 fff_plugin.py:1303 fff_plugin.py:1501 fff_plugin.py:1531
+#: dialogs.py:885 fff_plugin.py:1376 fff_plugin.py:1574 fff_plugin.py:1604
msgid "Comment"
msgstr ""
@@ -1241,603 +1249,623 @@ msgstr ""
msgid "Enter Email Password for %s:"
msgstr ""
-#: fff_plugin.py:114 fff_plugin.py:145
+#: fff_plugin.py:115 fff_plugin.py:146
msgid "FanFicFare"
msgstr ""
-#: fff_plugin.py:115
+#: fff_plugin.py:116
msgid "Download FanFiction stories from various web sites"
msgstr ""
-#: fff_plugin.py:276
+#: fff_plugin.py:277
msgid "&Download from URLs"
msgstr ""
-#: fff_plugin.py:278
+#: fff_plugin.py:279
msgid "Download FanFiction Books from URLs"
msgstr ""
-#: fff_plugin.py:281
+#: fff_plugin.py:282
msgid "&Update Existing FanFiction Books"
msgstr ""
-#: fff_plugin.py:286
+#: fff_plugin.py:287
msgid "Get Story URLs from &Email"
msgstr ""
-#: fff_plugin.py:290 fff_plugin.py:478
+#: fff_plugin.py:291 fff_plugin.py:487
msgid "Get Story URLs from Web Page"
msgstr ""
-#: fff_plugin.py:296
+#: fff_plugin.py:297
msgid "&Make Anthology Epub from URLs"
msgstr ""
-#: fff_plugin.py:298
+#: fff_plugin.py:299
msgid "Make FanFiction Anthology Epub from URLs"
msgstr ""
-#: fff_plugin.py:301
+#: fff_plugin.py:302
msgid "Make Anthology Epub from Web Page"
msgstr ""
-#: fff_plugin.py:303
+#: fff_plugin.py:304
msgid "Make FanFiction Anthology Epub from Web Page"
msgstr ""
-#: fff_plugin.py:306
+#: fff_plugin.py:307
msgid "Update Anthology Epub"
msgstr ""
-#: fff_plugin.py:308
+#: fff_plugin.py:309
msgid "Update FanFiction Anthology Epub"
msgstr ""
-#: fff_plugin.py:315
+#: fff_plugin.py:316
msgid "Mark Unread: Add to \"To Read\" and \"Send to Device\" Lists"
msgstr ""
-#: fff_plugin.py:317
+#: fff_plugin.py:318
msgid "Mark Read: Remove from \"To Read\" and add to \"Send to Device\" Lists"
msgstr ""
-#: fff_plugin.py:319 fff_plugin.py:324
+#: fff_plugin.py:320 fff_plugin.py:325
msgid "Mark Read: Remove from \"To Read\" Lists"
msgstr ""
-#: fff_plugin.py:321
+#: fff_plugin.py:322
msgid "Add to \"Send to Device\" Lists"
msgstr ""
-#: fff_plugin.py:323
+#: fff_plugin.py:324
msgid "Mark Unread: Add to \"To Read\" Lists"
msgstr ""
-#: fff_plugin.py:339
+#: fff_plugin.py:340
+msgid "Remove \"New\" Chapter Marks from Selected books"
+msgstr ""
+
+#: fff_plugin.py:346
msgid "Get Story URLs from Selected Books"
msgstr ""
-#: fff_plugin.py:344
+#: fff_plugin.py:351
msgid "Reject Selected Books"
msgstr ""
-#: fff_plugin.py:352
+#: fff_plugin.py:359
msgid "&Configure FanFicFare"
msgstr ""
-#: fff_plugin.py:355
+#: fff_plugin.py:362
msgid "Configure FanFicFare"
msgstr ""
-#: fff_plugin.py:410
+#: fff_plugin.py:417
msgid "Cannot Update Reading Lists from Device View"
msgstr ""
-#: fff_plugin.py:414
+#: fff_plugin.py:421
msgid "No Selected Books to Update Reading Lists"
msgstr ""
-#: fff_plugin.py:422
+#: fff_plugin.py:431
msgid "FanFicFare Email Settings are not configured."
msgstr ""
-#: fff_plugin.py:442
+#: fff_plugin.py:451
msgid "Fetching Story URLs from Email..."
msgstr ""
-#: fff_plugin.py:454
+#: fff_plugin.py:463
msgid "Finished Fetching Story URLs from Email."
msgstr ""
-#: fff_plugin.py:461
+#: fff_plugin.py:470
msgid "No Valid Story URLs Found in Unread Emails."
msgstr ""
-#: fff_plugin.py:463
+#: fff_plugin.py:472
msgid "(%d Story URLs Skipped, on Rejected URL List)"
msgstr ""
-#: fff_plugin.py:464
+#: fff_plugin.py:473
msgid "Get Story URLs from Email"
msgstr ""
-#: fff_plugin.py:487
+#: fff_plugin.py:496
msgid "Fetching Story URLs from Page..."
msgstr ""
-#: fff_plugin.py:491
+#: fff_plugin.py:500
msgid "Finished Fetching Story URLs from Page."
msgstr ""
-#: fff_plugin.py:497 fff_plugin.py:549
+#: fff_plugin.py:506 fff_plugin.py:558
msgid "List of Story URLs"
msgstr ""
-#: fff_plugin.py:498
+#: fff_plugin.py:507
msgid "No Valid Story URLs found on given page."
msgstr ""
-#: fff_plugin.py:513
+#: fff_plugin.py:522 fff_plugin.py:575
msgid "No Selected Books to Get URLs From"
msgstr ""
-#: fff_plugin.py:531
+#: fff_plugin.py:540
msgid "Collecting URLs for stories..."
msgstr ""
-#: fff_plugin.py:532
+#: fff_plugin.py:541
msgid "Get URLs for stories"
msgstr ""
-#: fff_plugin.py:533 fff_plugin.py:580 fff_plugin.py:773
+#: fff_plugin.py:542 fff_plugin.py:653 fff_plugin.py:846
msgid "URL retrieved"
msgstr ""
-#: fff_plugin.py:553
+#: fff_plugin.py:562
msgid "List of URLs"
msgstr ""
-#: fff_plugin.py:554
+#: fff_plugin.py:563
msgid "No Story URLs found in selected books."
msgstr ""
#: fff_plugin.py:570
-msgid "No Selected Books have URLs to Reject"
+msgid "Can only UnNew books in library"
msgstr ""
-#: fff_plugin.py:578
-msgid "Collecting URLs for Reject List..."
+#: fff_plugin.py:587
+msgid "UnNewing books..."
msgstr ""
-#: fff_plugin.py:579
-msgid "Get URLs for Reject List"
+#: fff_plugin.py:588
+msgid "UnNew Books"
msgstr ""
-#: fff_plugin.py:614
-msgid "Proceed to Remove?"
+#: fff_plugin.py:589
+msgid "Books UnNewed"
msgstr ""
-#: fff_plugin.py:614
-msgid "Rejecting FanFicFare URLs: None of the books selected have FanFiction URLs."
-msgstr ""
-
-#: fff_plugin.py:636
-msgid "Cannot Make Anthologys without %s"
-msgstr ""
-
-#: fff_plugin.py:640 fff_plugin.py:750
-msgid "Cannot Update Books from Device View"
-msgstr ""
-
-#: fff_plugin.py:644
-msgid "Can only update 1 anthology at a time"
-msgstr ""
-
-#: fff_plugin.py:653
-msgid "Can only Update Epub Anthologies"
-msgstr ""
-
-#: fff_plugin.py:671 fff_plugin.py:672
-msgid "Cannot Update Anthology"
-msgstr ""
-
-#: fff_plugin.py:672
-msgid "Book isn't an FanFicFare Anthology or contains book(s) without valid Story URLs."
-msgstr ""
-
-#: fff_plugin.py:679
-msgid "Fetching Story URLs for Series..."
-msgstr ""
-
-#: fff_plugin.py:689
-msgid "Finished Fetching Story URLs for Series."
-msgstr ""
-
-#: fff_plugin.py:736
-msgid "There are %d stories in the current anthology that are not going to be kept if you go ahead."
-msgstr ""
-
-#: fff_plugin.py:737
-msgid "Story URLs that will be removed:"
-msgstr ""
-
-#: fff_plugin.py:739
-msgid "Update anyway?"
-msgstr ""
-
-#: fff_plugin.py:740
-msgid "Stories Removed"
-msgstr ""
-
-#: fff_plugin.py:757
-msgid "No Selected Books to Update"
-msgstr ""
-
-#: fff_plugin.py:771
-msgid "Collecting stories for update..."
-msgstr ""
-
-#: fff_plugin.py:772
-msgid "Get stories for updates"
-msgstr ""
-
-#: fff_plugin.py:782
-msgid "Update Existing List"
-msgstr ""
-
-#: fff_plugin.py:841
-msgid "Started fetching metadata for %s stories."
-msgstr ""
-
-#: fff_plugin.py:847
-msgid "No valid story URLs entered."
-msgstr ""
-
-#: fff_plugin.py:872 fff_plugin.py:878
-msgid "Reject URL?"
-msgstr ""
-
-#: fff_plugin.py:879 fff_plugin.py:897
-msgid "%s is on your Reject URL list:"
-msgstr ""
-
-#: fff_plugin.py:881
-msgid "Click 'Yes' to Reject."
-msgstr ""
-
-#: fff_plugin.py:882 fff_plugin.py:1009
-msgid "Click 'No' to download anyway."
-msgstr ""
-
-#: fff_plugin.py:884
-msgid "Story on Reject URLs list (%s)."
-msgstr ""
-
-#: fff_plugin.py:887
-msgid "Rejected"
-msgstr ""
-
-#: fff_plugin.py:890
-msgid "Remove Reject URL?"
-msgstr ""
-
-#: fff_plugin.py:896
-msgid "Remove URL from Reject List?"
-msgstr ""
-
-#: fff_plugin.py:899
-msgid "Click 'Yes' to remove it from the list,"
-msgstr ""
-
-#: fff_plugin.py:900
-msgid "Click 'No' to leave it on the list."
-msgstr ""
-
-#: fff_plugin.py:917
-msgid "Cannot update non-epub format."
-msgstr ""
-
-#: fff_plugin.py:987
-msgid "Are You an Adult?"
-msgstr ""
-
-#: fff_plugin.py:988
-msgid "%s requires that you be an adult. Please confirm you are an adult in your locale:"
-msgstr ""
-
-#: fff_plugin.py:1000
-msgid "Skip Story?"
-msgstr ""
-
-#: fff_plugin.py:1006
-msgid "Skip Anthology Story?"
-msgstr ""
-
-#: fff_plugin.py:1007
-msgid "\"%s\" is in series \"%s\" that you have an anthology book for."
-msgstr ""
-
-#: fff_plugin.py:1008
-msgid "Click 'Yes' to Skip."
-msgstr ""
-
-#: fff_plugin.py:1011
-msgid "Story in Series Anthology(%s)."
-msgstr ""
-
-#: fff_plugin.py:1016
-msgid "Skipped"
-msgstr ""
-
-#: fff_plugin.py:1046
-msgid "Add"
-msgstr ""
-
-#: fff_plugin.py:1059
-msgid "Meta"
-msgstr ""
-
-#: fff_plugin.py:1090
-msgid "Skipping duplicate story."
-msgstr ""
-
-#: fff_plugin.py:1093
-msgid "More than one identical book by Identifer URL or title/author(s)--can't tell which book to update/overwrite."
-msgstr ""
-
-#: fff_plugin.py:1104
-msgid "Update"
-msgstr ""
-
-#: fff_plugin.py:1112 fff_plugin.py:1119
-msgid "Change Story URL?"
-msgstr ""
-
-#: fff_plugin.py:1120
-msgid "%s by %s is already in your library with a different source URL:"
-msgstr ""
-
-#: fff_plugin.py:1121
-msgid "In library: %(liburl)s"
-msgstr ""
-
-#: fff_plugin.py:1122 fff_plugin.py:1136
-msgid "New URL: %(newurl)s"
-msgstr ""
-
-#: fff_plugin.py:1123
-msgid "Click 'Yes' to update/overwrite book with new URL."
-msgstr ""
-
-#: fff_plugin.py:1124
-msgid "Click 'No' to skip updating/overwriting this book."
-msgstr ""
-
-#: fff_plugin.py:1126 fff_plugin.py:1133
-msgid "Download as New Book?"
-msgstr ""
-
-#: fff_plugin.py:1134
-msgid "%s by %s is already in your library with a different source URL."
-msgstr ""
-
-#: fff_plugin.py:1135
-msgid "You chose not to update the existing book. Do you want to add a new book for this URL?"
-msgstr ""
-
-#: fff_plugin.py:1137
-msgid "Click 'Yes' to a new book with new URL."
-msgstr ""
-
-#: fff_plugin.py:1138
-msgid "Click 'No' to skip URL."
-msgstr ""
-
-#: fff_plugin.py:1144
-msgid "Update declined by user due to differing story URL(%s)"
-msgstr ""
-
-#: fff_plugin.py:1147
-msgid "Different URL"
-msgstr ""
-
-#: fff_plugin.py:1152
-msgid "Metadata collected."
-msgstr ""
-
-#: fff_plugin.py:1168
-msgid "Already contains %d chapters."
-msgstr ""
-
-#: fff_plugin.py:1170 jobs.py:209
-msgid "Existing epub contains %d chapters, web site only has %d. Use Overwrite to force update."
-msgstr ""
-
-#: fff_plugin.py:1172
-msgid "FanFicFare doesn't recognize chapters in existing epub, epub is probably from a different source. Use Overwrite to force update."
-msgstr ""
-
-#: fff_plugin.py:1184
-msgid "Not Overwriting, web site is not newer."
-msgstr ""
-
-#: fff_plugin.py:1299
-msgid "None of the %d URLs/stories given can be/need to be downloaded."
-msgstr ""
-
-#: fff_plugin.py:1300 fff_plugin.py:1497 fff_plugin.py:1527
-msgid "See log for details."
-msgstr ""
-
-#: fff_plugin.py:1301
-msgid "Proceed with updating your library(Error Column, if configured)?"
-msgstr ""
-
-#: fff_plugin.py:1308 fff_plugin.py:1509
-msgid "Bad"
-msgstr ""
-
-#: fff_plugin.py:1316
-msgid "FanFicFare download ended"
-msgstr ""
-
-#: fff_plugin.py:1316 fff_plugin.py:1552
-msgid "FanFicFare log"
-msgstr ""
-
-#: fff_plugin.py:1336
-msgid "Download FanFiction Book"
-msgstr ""
-
-#: fff_plugin.py:1343
-msgid "Starting %d FanFicFare Downloads"
-msgstr ""
-
-#: fff_plugin.py:1373
-msgid "Story Details:"
-msgstr ""
-
-#: fff_plugin.py:1376
-msgid "Error Updating Metadata"
-msgstr ""
-
-#: fff_plugin.py:1377
-msgid "An error has occurred while FanFicFare was updating calibre's metadata for %s."
-msgstr ""
-
-#: fff_plugin.py:1378
-msgid "The ebook has been updated, but the metadata has not."
-msgstr ""
-
-#: fff_plugin.py:1430
-msgid "Finished Adding/Updating %d books."
-msgstr ""
-
-#: fff_plugin.py:1460
+#: fff_plugin.py:628 fff_plugin.py:1533
msgid "Starting auto conversion of %d books."
msgstr ""
-#: fff_plugin.py:1481
+#: fff_plugin.py:643
+msgid "No Selected Books have URLs to Reject"
+msgstr ""
+
+#: fff_plugin.py:651
+msgid "Collecting URLs for Reject List..."
+msgstr ""
+
+#: fff_plugin.py:652
+msgid "Get URLs for Reject List"
+msgstr ""
+
+#: fff_plugin.py:687
+msgid "Proceed to Remove?"
+msgstr ""
+
+#: fff_plugin.py:687
+msgid "Rejecting FanFicFare URLs: None of the books selected have FanFiction URLs."
+msgstr ""
+
+#: fff_plugin.py:709
+msgid "Cannot Make Anthologys without %s"
+msgstr ""
+
+#: fff_plugin.py:713 fff_plugin.py:823
+msgid "Cannot Update Books from Device View"
+msgstr ""
+
+#: fff_plugin.py:717
+msgid "Can only update 1 anthology at a time"
+msgstr ""
+
+#: fff_plugin.py:726
+msgid "Can only Update Epub Anthologies"
+msgstr ""
+
+#: fff_plugin.py:744 fff_plugin.py:745
+msgid "Cannot Update Anthology"
+msgstr ""
+
+#: fff_plugin.py:745
+msgid "Book isn't an FanFicFare Anthology or contains book(s) without valid Story URLs."
+msgstr ""
+
+#: fff_plugin.py:752
+msgid "Fetching Story URLs for Series..."
+msgstr ""
+
+#: fff_plugin.py:762
+msgid "Finished Fetching Story URLs for Series."
+msgstr ""
+
+#: fff_plugin.py:809
+msgid "There are %d stories in the current anthology that are not going to be kept if you go ahead."
+msgstr ""
+
+#: fff_plugin.py:810
+msgid "Story URLs that will be removed:"
+msgstr ""
+
+#: fff_plugin.py:812
+msgid "Update anyway?"
+msgstr ""
+
+#: fff_plugin.py:813
+msgid "Stories Removed"
+msgstr ""
+
+#: fff_plugin.py:830
+msgid "No Selected Books to Update"
+msgstr ""
+
+#: fff_plugin.py:844
+msgid "Collecting stories for update..."
+msgstr ""
+
+#: fff_plugin.py:845
+msgid "Get stories for updates"
+msgstr ""
+
+#: fff_plugin.py:855
+msgid "Update Existing List"
+msgstr ""
+
+#: fff_plugin.py:914
+msgid "Started fetching metadata for %s stories."
+msgstr ""
+
+#: fff_plugin.py:920
+msgid "No valid story URLs entered."
+msgstr ""
+
+#: fff_plugin.py:945 fff_plugin.py:951
+msgid "Reject URL?"
+msgstr ""
+
+#: fff_plugin.py:952 fff_plugin.py:970
+msgid "%s is on your Reject URL list:"
+msgstr ""
+
+#: fff_plugin.py:954
+msgid "Click 'Yes' to Reject."
+msgstr ""
+
+#: fff_plugin.py:955 fff_plugin.py:1082
+msgid "Click 'No' to download anyway."
+msgstr ""
+
+#: fff_plugin.py:957
+msgid "Story on Reject URLs list (%s)."
+msgstr ""
+
+#: fff_plugin.py:960
+msgid "Rejected"
+msgstr ""
+
+#: fff_plugin.py:963
+msgid "Remove Reject URL?"
+msgstr ""
+
+#: fff_plugin.py:969
+msgid "Remove URL from Reject List?"
+msgstr ""
+
+#: fff_plugin.py:972
+msgid "Click 'Yes' to remove it from the list,"
+msgstr ""
+
+#: fff_plugin.py:973
+msgid "Click 'No' to leave it on the list."
+msgstr ""
+
+#: fff_plugin.py:990
+msgid "Cannot update non-epub format."
+msgstr ""
+
+#: fff_plugin.py:1060
+msgid "Are You an Adult?"
+msgstr ""
+
+#: fff_plugin.py:1061
+msgid "%s requires that you be an adult. Please confirm you are an adult in your locale:"
+msgstr ""
+
+#: fff_plugin.py:1073
+msgid "Skip Story?"
+msgstr ""
+
+#: fff_plugin.py:1079
+msgid "Skip Anthology Story?"
+msgstr ""
+
+#: fff_plugin.py:1080
+msgid "\"%s\" is in series \"%s\" that you have an anthology book for."
+msgstr ""
+
+#: fff_plugin.py:1081
+msgid "Click 'Yes' to Skip."
+msgstr ""
+
+#: fff_plugin.py:1084
+msgid "Story in Series Anthology(%s)."
+msgstr ""
+
+#: fff_plugin.py:1089
+msgid "Skipped"
+msgstr ""
+
+#: fff_plugin.py:1119
+msgid "Add"
+msgstr ""
+
+#: fff_plugin.py:1132
+msgid "Meta"
+msgstr ""
+
+#: fff_plugin.py:1163
+msgid "Skipping duplicate story."
+msgstr ""
+
+#: fff_plugin.py:1166
+msgid "More than one identical book by Identifer URL or title/author(s)--can't tell which book to update/overwrite."
+msgstr ""
+
+#: fff_plugin.py:1177
+msgid "Update"
+msgstr ""
+
+#: fff_plugin.py:1185 fff_plugin.py:1192
+msgid "Change Story URL?"
+msgstr ""
+
+#: fff_plugin.py:1193
+msgid "%s by %s is already in your library with a different source URL:"
+msgstr ""
+
+#: fff_plugin.py:1194
+msgid "In library: %(liburl)s"
+msgstr ""
+
+#: fff_plugin.py:1195 fff_plugin.py:1209
+msgid "New URL: %(newurl)s"
+msgstr ""
+
+#: fff_plugin.py:1196
+msgid "Click 'Yes' to update/overwrite book with new URL."
+msgstr ""
+
+#: fff_plugin.py:1197
+msgid "Click 'No' to skip updating/overwriting this book."
+msgstr ""
+
+#: fff_plugin.py:1199 fff_plugin.py:1206
+msgid "Download as New Book?"
+msgstr ""
+
+#: fff_plugin.py:1207
+msgid "%s by %s is already in your library with a different source URL."
+msgstr ""
+
+#: fff_plugin.py:1208
+msgid "You chose not to update the existing book. Do you want to add a new book for this URL?"
+msgstr ""
+
+#: fff_plugin.py:1210
+msgid "Click 'Yes' to a new book with new URL."
+msgstr ""
+
+#: fff_plugin.py:1211
+msgid "Click 'No' to skip URL."
+msgstr ""
+
+#: fff_plugin.py:1217
+msgid "Update declined by user due to differing story URL(%s)"
+msgstr ""
+
+#: fff_plugin.py:1220
+msgid "Different URL"
+msgstr ""
+
+#: fff_plugin.py:1225
+msgid "Metadata collected."
+msgstr ""
+
+#: fff_plugin.py:1241
+msgid "Already contains %d chapters."
+msgstr ""
+
+#: fff_plugin.py:1243 jobs.py:211
+msgid "Existing epub contains %d chapters, web site only has %d. Use Overwrite to force update."
+msgstr ""
+
+#: fff_plugin.py:1245
+msgid "FanFicFare doesn't recognize chapters in existing epub, epub is probably from a different source. Use Overwrite to force update."
+msgstr ""
+
+#: fff_plugin.py:1257
+msgid "Not Overwriting, web site is not newer."
+msgstr ""
+
+#: fff_plugin.py:1372
+msgid "None of the %d URLs/stories given can be/need to be downloaded."
+msgstr ""
+
+#: fff_plugin.py:1373 fff_plugin.py:1570 fff_plugin.py:1600
+msgid "See log for details."
+msgstr ""
+
+#: fff_plugin.py:1374
+msgid "Proceed with updating your library(Error Column, if configured)?"
+msgstr ""
+
+#: fff_plugin.py:1381 fff_plugin.py:1582
+msgid "Bad"
+msgstr ""
+
+#: fff_plugin.py:1389
+msgid "FanFicFare download ended"
+msgstr ""
+
+#: fff_plugin.py:1389 fff_plugin.py:1625
+msgid "FanFicFare log"
+msgstr ""
+
+#: fff_plugin.py:1409
+msgid "Download FanFiction Book"
+msgstr ""
+
+#: fff_plugin.py:1416
+msgid "Starting %d FanFicFare Downloads"
+msgstr ""
+
+#: fff_plugin.py:1446
+msgid "Story Details:"
+msgstr ""
+
+#: fff_plugin.py:1449
+msgid "Error Updating Metadata"
+msgstr ""
+
+#: fff_plugin.py:1450
+msgid "An error has occurred while FanFicFare was updating calibre's metadata for %s."
+msgstr ""
+
+#: fff_plugin.py:1451
+msgid "The ebook has been updated, but the metadata has not."
+msgstr ""
+
+#: fff_plugin.py:1503
+msgid "Finished Adding/Updating %d books."
+msgstr ""
+
+#: fff_plugin.py:1554
msgid "No Good Stories for Anthology"
msgstr ""
-#: fff_plugin.py:1482
+#: fff_plugin.py:1555
msgid "No good stories/updates where downloaded, Anthology creation/update aborted."
msgstr ""
-#: fff_plugin.py:1487 fff_plugin.py:1526
+#: fff_plugin.py:1560 fff_plugin.py:1599
msgid "FanFicFare found %s good and %s bad updates."
msgstr ""
-#: fff_plugin.py:1494
+#: fff_plugin.py:1567
msgid "Are you sure you want to continue with creating/updating this Anthology?"
msgstr ""
-#: fff_plugin.py:1495
+#: fff_plugin.py:1568
msgid "Any updates that failed will not be included in the Anthology."
msgstr ""
-#: fff_plugin.py:1496
+#: fff_plugin.py:1569
msgid "However, if there's an older version, it will still be included."
msgstr ""
-#: fff_plugin.py:1499
+#: fff_plugin.py:1572
msgid "Proceed with updating this anthology and your library?"
msgstr ""
-#: fff_plugin.py:1507
+#: fff_plugin.py:1580
msgid "Good"
msgstr ""
-#: fff_plugin.py:1528
+#: fff_plugin.py:1601
msgid "Proceed with updating your library?"
msgstr ""
-#: fff_plugin.py:1552
+#: fff_plugin.py:1625
msgid "FanFicFare download complete"
msgstr ""
-#: fff_plugin.py:1565
+#: fff_plugin.py:1638
msgid "Merging %s books."
msgstr ""
-#: fff_plugin.py:1605
+#: fff_plugin.py:1678
msgid "FanFicFare Adding/Updating books."
msgstr ""
-#: fff_plugin.py:1612
+#: fff_plugin.py:1685
msgid "Updating calibre for FanFiction stories..."
msgstr ""
-#: fff_plugin.py:1613
+#: fff_plugin.py:1686
msgid "Update calibre for FanFiction stories"
msgstr ""
-#: fff_plugin.py:1622
+#: fff_plugin.py:1695
msgid "Adding/Updating %s BAD books."
msgstr ""
-#: fff_plugin.py:1631
+#: fff_plugin.py:1704
msgid "Updating calibre for BAD FanFiction stories..."
msgstr ""
-#: fff_plugin.py:1632
+#: fff_plugin.py:1705
msgid "Update calibre for BAD FanFiction stories"
msgstr ""
-#: fff_plugin.py:1658
+#: fff_plugin.py:1731
msgid "Adding format to book failed for some reason..."
msgstr ""
-#: fff_plugin.py:1661
+#: fff_plugin.py:1734
msgid "Error"
msgstr ""
-#: fff_plugin.py:1974
+#: fff_plugin.py:2047
msgid "You configured FanFicFare to automatically update Reading Lists, but you don't have the %s plugin installed anymore?"
msgstr ""
-#: fff_plugin.py:1986
+#: fff_plugin.py:2059
msgid "You configured FanFicFare to automatically update \"To Read\" Reading Lists, but you don't have any lists set?"
msgstr ""
-#: fff_plugin.py:1996 fff_plugin.py:2014
+#: fff_plugin.py:2069 fff_plugin.py:2087
msgid "You configured FanFicFare to automatically update Reading List '%s', but you don't have a list of that name?"
msgstr ""
-#: fff_plugin.py:2002
+#: fff_plugin.py:2075
msgid "You configured FanFicFare to automatically update \"Send to Device\" Reading Lists, but you don't have any lists set?"
msgstr ""
-#: fff_plugin.py:2123
+#: fff_plugin.py:2196
msgid "No story URL found."
msgstr ""
-#: fff_plugin.py:2126
+#: fff_plugin.py:2199
msgid "Not Found"
msgstr ""
-#: fff_plugin.py:2132
+#: fff_plugin.py:2205
msgid "URL is not a valid story URL."
msgstr ""
-#: fff_plugin.py:2135
+#: fff_plugin.py:2208
msgid "Bad URL"
msgstr ""
-#: fff_plugin.py:2274 fff_plugin.py:2277
+#: fff_plugin.py:2347 fff_plugin.py:2350
msgid "Anthology containing:"
msgstr ""
-#: fff_plugin.py:2275
+#: fff_plugin.py:2348
msgid "%s by %s"
msgstr ""
-#: fff_plugin.py:2297
+#: fff_plugin.py:2370
msgid " Anthology"
msgstr ""
-#: fff_plugin.py:2340
+#: fff_plugin.py:2413
msgid "(was set, removed for security)"
msgstr ""
@@ -1857,11 +1885,11 @@ msgstr ""
msgid "Download started..."
msgstr ""
-#: jobs.py:200
+#: jobs.py:202
msgid "Already contains %d chapters. Reuse as is."
msgstr ""
-#: jobs.py:221
+#: jobs.py:223
msgid "Update %s completed, added %s chapters for %s total."
msgstr ""
diff --git a/fanficfare/adapters/__init__.py b/fanficfare/adapters/__init__.py
index d0fd651..6c4e1c8 100644
--- a/fanficfare/adapters/__init__.py
+++ b/fanficfare/adapters/__init__.py
@@ -135,6 +135,9 @@ import adapter_fanfictionjunkiesde
import adapter_devianthearts
import adapter_tgstorytimecom
import adapter_itcouldhappennet
+import adapter_forumsspacebattlescom
+import adapter_forumssufficientvelocitycom
+import adapter_ninelivesdarksolaceorg
import adapter_masseffect2in
## This bit of complexity allows adapters to be added by just adding
@@ -195,14 +198,24 @@ def getAdapter(config,url,anyurl=False):
# No adapter found.
raise exceptions.UnknownSite( url, [cls.getSiteDomain() for cls in __class_list] )
-def getConfigSections():
+def getSiteSections():
+ # doesn't include base sections. Sections rather than site DNS because of squidge/peja
return [cls.getConfigSection() for cls in __class_list]
+def getConfigSections():
+ # does include base sections.
+ sections = set()
+ for cls in __class_list:
+ sections.update(cls.getConfigSections())
+ return sections
+
def get_bulk_load_sites():
# for now, all eFiction Base adapters are assumed to allow bulk_load.
- return [cls.getConfigSection().replace('www.','') for cls in
- filter( lambda x : issubclass(x,base_efiction_adapter.BaseEfictionAdapter),
- __class_list)]
+ sections = set()
+ for cls in filter( lambda x : issubclass(x,base_efiction_adapter.BaseEfictionAdapter),
+ __class_list):
+ sections.update( [ x.replace('www.','') for x in cls.getConfigSections() ] )
+ return sections
def getSiteExamples():
l=[]
@@ -210,10 +223,10 @@ def getSiteExamples():
l.append((cls.getConfigSection(),cls.getSiteExampleURLs().split()))
return l
-def getConfigSectionFor(url):
+def getConfigSectionsFor(url):
(cls,fixedurl) = getClassFor(url)
if cls:
- return cls.getConfigSection()
+ return cls.getConfigSections()
# No adapter found.
raise exceptions.UnknownSite( url, [cls.getSiteDomain() for cls in __class_list] )
diff --git a/fanficfare/adapters/adapter_forumsspacebattlescom.py b/fanficfare/adapters/adapter_forumsspacebattlescom.py
new file mode 100644
index 0000000..0a58c65
--- /dev/null
+++ b/fanficfare/adapters/adapter_forumsspacebattlescom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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
+
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_xenforoforum_adapter import BaseXenForoForumAdapter
+
+def getClass():
+ return ForumsSpacebattlesComAdapter
+
+class ForumsSpacebattlesComAdapter(BaseXenForoForumAdapter):
+
+ def __init__(self, config, url):
+ BaseXenForoForumAdapter.__init__(self, config, url)
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','fsb')
+
+ @staticmethod # must be @staticmethod, don't remove it.
+ def getSiteDomain():
+ # The site domain. Does have www here, if it uses it.
+ return 'forums.spacebattles.com'
+
+ @classmethod
+ def getURLPrefix(cls):
+ return 'https://' + cls.getSiteDomain()
+
diff --git a/fanficfare/adapters/adapter_forumssufficientvelocitycom.py b/fanficfare/adapters/adapter_forumssufficientvelocitycom.py
new file mode 100644
index 0000000..883dc74
--- /dev/null
+++ b/fanficfare/adapters/adapter_forumssufficientvelocitycom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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.
+#
+
+from base_xenforoforum_adapter import BaseXenForoForumAdapter
+
+def getClass():
+ return ForumsSufficientVelocityComAdapter
+
+class ForumsSufficientVelocityComAdapter(BaseXenForoForumAdapter):
+
+ def __init__(self, config, url):
+ BaseXenForoForumAdapter.__init__(self, config, url)
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','fsv')
+
+ @staticmethod # must be @staticmethod, don't remove it.
+ def getSiteDomain():
+ # The site domain. Does have www here, if it uses it.
+ return 'forums.sufficientvelocity.com'
+
+ @classmethod
+ def getURLPrefix(cls):
+ return 'http://' + cls.getSiteDomain()
diff --git a/fanficfare/adapters/adapter_ninelivesdarksolaceorg.py b/fanficfare/adapters/adapter_ninelivesdarksolaceorg.py
new file mode 100644
index 0000000..22f9e78
--- /dev/null
+++ b/fanficfare/adapters/adapter_ninelivesdarksolaceorg.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2015 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 re
+from base_efiction_adapter import BaseEfictionAdapter
+
+class NineLivesDarkSolaceAdapter(BaseEfictionAdapter):
+
+ @staticmethod
+ def getSiteDomain():
+ return 'ninelives.dark-solace.org'
+
+ @classmethod
+ def getSiteAbbrev(self):
+ return '9lvs'
+
+ @classmethod
+ def getDateFormat(self):
+ return "%B %d, %Y"
+
+def getClass():
+ return NineLivesDarkSolaceAdapter
+
diff --git a/fanficfare/adapters/adapter_spikeluvercom.py b/fanficfare/adapters/adapter_spikeluvercom.py
index 937789b..bee71d3 100644
--- a/fanficfare/adapters/adapter_spikeluvercom.py
+++ b/fanficfare/adapters/adapter_spikeluvercom.py
@@ -106,7 +106,9 @@ class SpikeluverComAdapter(BaseSiteAdapter):
listbox_tag = soup.find('div', {'class': 'listbox'})
for span_tag in listbox_tag('span'):
- key = span_tag.string.strip(' :')
+ key = span_tag.string
+ if key:
+ key = key.strip(' :')
try:
value = stripHTML(span_tag.nextSibling)
# This can happen with some fancy markup in the summary. Just
@@ -135,8 +137,10 @@ class SpikeluverComAdapter(BaseSiteAdapter):
contents.append(sibling)
# Remove the preceding break line tag and other crud
- contents.pop()
- contents.pop()
+ if contents:
+ contents.pop()
+ if contents:
+ contents.pop()
self.story.setMetadata('description', ''.join(contents))
elif key == 'Rated':
diff --git a/fanficfare/adapters/base_adapter.py b/fanficfare/adapters/base_adapter.py
index 2b76d69..3bb7669 100644
--- a/fanficfare/adapters/base_adapter.py
+++ b/fanficfare/adapters/base_adapter.py
@@ -104,6 +104,8 @@ class BaseSiteAdapter(Configurable):
self.chapterFirst = None
self.chapterLast = None
self.oldchapters = None
+ self.oldchaptersmap = None
+ self.oldchaptersdata = None
self.oldimgs = None
self.oldcover = None # (data of existing cover html, data of existing cover image)
self.calibrebookmark = None
@@ -140,28 +142,6 @@ class BaseSiteAdapter(Configurable):
'''
self.get_cookiejar().load(filename, ignore_discard=True, ignore_expires=True)
- # def save_cookiejar(self,filename):
- # '''
- # Assumed to be a FileCookieJar if self.cookiejar set.
- # Takes file *name*.
- # '''
- # self.get_cookiejar().save(filename, ignore_discard=True, ignore_expires=True)
-
- # def save_pagecache(self,filename):
- # '''
- # Writes pickle of pagecache to file *name*
- # '''
- # with open(filename, 'wb') as f:
- # pickle.dump(self.get_pagecache(),
- # f,protocol=pickle.HIGHEST_PROTOCOL)
-
- # def load_pagecache(self,filename):
- # '''
- # Reads pickle of pagecache from file *name*
- # '''
- # with open(filename, 'rb') as f:
- # self.set_pagecache(pickle.load(f))
-
def get_pagecache(self):
return self.pagecache
@@ -185,9 +165,9 @@ class BaseSiteAdapter(Configurable):
else:
return None
- def _set_to_pagecache(self,cachekey,data):
+ def _set_to_pagecache(self,cachekey,data,redirectedurl):
if self.use_pagecache():
- self.get_pagecache()[cachekey] = data
+ self.get_pagecache()[cachekey] = (data,redirectedurl)
def use_pagecache(self):
'''
@@ -257,7 +237,8 @@ class BaseSiteAdapter(Configurable):
cachekey=self._get_cachekey(url, parameters, headers)
if usecache and self._has_cachekey(cachekey):
logger.debug("#####################################\npagecache HIT: %s"%cachekey)
- return self._get_from_pagecache(cachekey)
+ data,redirecturl = self._get_from_pagecache(cachekey)
+ return data
logger.debug("#####################################\npagecache MISS: %s"%cachekey)
self.do_sleep(extrasleep)
@@ -272,13 +253,23 @@ class BaseSiteAdapter(Configurable):
data=urllib.urlencode(parameters),
headers=headers)
data = self._decode(self.opener.open(req,None,float(self.getConfig('connect_timeout',30.0))).read())
- self._set_to_pagecache(cachekey,data)
+ self._set_to_pagecache(cachekey,data,url)
return data
def _fetchUrlRaw(self, url,
parameters=None,
extrasleep=None,
usecache=True):
+
+ return self._fetchUrlRawOpened(url,
+ parameters,
+ extrasleep,
+ usecache)[0]
+
+ def _fetchUrlRawOpened(self, url,
+ parameters=None,
+ extrasleep=None,
+ usecache=True):
'''
When should cache be cleared or not used? logins...
@@ -289,16 +280,25 @@ class BaseSiteAdapter(Configurable):
cachekey=self._get_cachekey(url, parameters)
if usecache and self._has_cachekey(cachekey):
logger.debug("#####################################\npagecache HIT: %s"%cachekey)
- return self._get_from_pagecache(cachekey)
+ data,redirecturl = self._get_from_pagecache(cachekey)
+ class FakeOpened:
+ def __init__(self,data,url):
+ self.data=data
+ self.url=url
+ def geturl(self): return self.url
+ def read(self): return self.data
+ return (data,FakeOpened(data,redirecturl))
logger.debug("#####################################\npagecache MISS: %s"%cachekey)
self.do_sleep(extrasleep)
if parameters != None:
- data = self.opener.open(url.replace(' ','%20'),urllib.urlencode(parameters),float(self.getConfig('connect_timeout',30.0))).read()
+ opened = self.opener.open(url.replace(' ','%20'),urllib.urlencode(parameters),float(self.getConfig('connect_timeout',30.0)))
else:
- data = self.opener.open(url.replace(' ','%20'),None,float(self.getConfig('connect_timeout',30.0))).read()
- self._set_to_pagecache(cachekey,data)
- return data
+ opened = self.opener.open(url.replace(' ','%20'),None,float(self.getConfig('connect_timeout',30.0)))
+ data = opened.read()
+ self._set_to_pagecache(cachekey,data,opened.url)
+
+ return (data,opened)
def set_sleep(self,val):
logger.debug("\n===========\n set sleep time %s\n==========="%val)
@@ -312,20 +312,30 @@ class BaseSiteAdapter(Configurable):
elif self.getConfig('slow_down_sleep_time'):
time.sleep(float(self.getConfig('slow_down_sleep_time')))
- # parameters is a dict()
def _fetchUrl(self, url,
parameters=None,
usecache=True,
extrasleep=None):
+ return self._fetchUrlOpened(url,
+ parameters,
+ usecache,
+ extrasleep)[0]
+
+ # parameters is a dict()
+ def _fetchUrlOpened(self, url,
+ parameters=None,
+ usecache=True,
+ extrasleep=None):
excpt=None
for sleeptime in [0, 0.5, 4, 9]:
time.sleep(sleeptime)
try:
- return self._decode(self._fetchUrlRaw(url,
+ (data,opened)=self._fetchUrlRawOpened(url,
parameters=parameters,
usecache=usecache,
- extrasleep=extrasleep))
+ extrasleep=extrasleep)
+ return (self._decode(data),opened)
except u2.HTTPError, he:
excpt=he
if he.code == 404:
@@ -352,21 +362,40 @@ class BaseSiteAdapter(Configurable):
self.getStoryMetadataOnly(get_cover=True)
for index, (title,url) in enumerate(self.chapterUrls):
+ newchap = False
if (self.chapterFirst!=None and index < self.chapterFirst) or \
(self.chapterLast!=None and index > self.chapterLast):
self.story.addChapter(url,
removeEntities(title),
None)
else:
- if self.oldchapters and index < len(self.oldchapters):
+ data = None
+ if self.oldchaptersmap:
+ if url in self.oldchaptersmap:
+ data = self.utf8FromSoup(None,
+ self.oldchaptersmap[url],
+ partial(cachedfetch,self._fetchUrlRaw,self.oldimgs))
+ elif self.oldchapters and index < len(self.oldchapters):
data = self.utf8FromSoup(None,
self.oldchapters[index],
partial(cachedfetch,self._fetchUrlRaw,self.oldimgs))
- else:
+
+ # if already marked new -- ie, origtitle and title don't match
+ # logger.debug("self.oldchaptersdata[url]:%s"%(self.oldchaptersdata[url]))
+ newchap = (self.oldchaptersdata is not None and
+ url in self.oldchaptersdata and (
+ self.oldchaptersdata[url]['chapterorigtitle'] !=
+ self.oldchaptersdata[url]['chaptertitle']) )
+
+ if not data:
data = self.getChapterText(url)
+ # if had to fetch and has existing chapters
+ newchap = bool(self.oldchapters or self.oldchaptersmap)
+
self.story.addChapter(url,
removeEntities(title),
- removeEntities(data))
+ removeEntities(data),
+ newchap)
self.storyDone = True
# include image, but no cover from story, add default_cover_image cover.
@@ -399,7 +428,10 @@ class BaseSiteAdapter(Configurable):
self.doExtractChapterUrlsAndMetadata(get_cover=get_cover)
if not self.story.getMetadataRaw('dateUpdated'):
- self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished'))
+ if self.story.getMetadataRaw('datePublished'):
+ self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished'))
+ else:
+ self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('dateCreated'))
self.metadataDone = True
return self.story
@@ -409,7 +441,10 @@ class BaseSiteAdapter(Configurable):
self.story.load_html_metadata(metahtml)
self.metadataDone = True
if not self.story.getMetadataRaw('dateUpdated'):
- self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished'))
+ if self.story.getMetadataRaw('datePublished'):
+ self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished'))
+ else:
+ self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('dateCreated'))
def hookForUpdates(self,chaptercount):
"Usually not needed."
@@ -427,6 +462,11 @@ class BaseSiteAdapter(Configurable):
"Only needs to be overriden if != site domain."
return cls.getSiteDomain()
+ @classmethod
+ def getConfigSections(cls):
+ "Only needs to be overriden if has additional ini sections."
+ return [cls.getConfigSection()]
+
@classmethod
def stripURLParameters(cls,url):
"Only needs to be overriden if URL contains more than one parameter"
@@ -476,6 +516,13 @@ class BaseSiteAdapter(Configurable):
def setDescription(self,url,svalue):
#print("\n\nsvalue:\n%s\n"%svalue)
+ strval = u"%s"%svalue # works for either soup or string
+ if self.hasConfig('description_limit'):
+ limit = int(self.getConfig('description_limit'))
+ if limit and len(strval) > limit:
+ svalue = strval[:limit]
+
+ #print(u"[[[[[\n\n%s\n\n]]]]]]]]"%svalue) # works for either soup or string
if self.getConfig('keep_summary_html'):
if isinstance(svalue,basestring):
# bs4/html5lib add html, header and body tags, which
diff --git a/fanficfare/adapters/base_efiction_adapter.py b/fanficfare/adapters/base_efiction_adapter.py
index 354072f..7d0fa23 100644
--- a/fanficfare/adapters/base_efiction_adapter.py
+++ b/fanficfare/adapters/base_efiction_adapter.py
@@ -70,6 +70,11 @@ class BaseEfictionAdapter(BaseSiteAdapter):
self.triedAcceptWarnings = False
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
+ @classmethod
+ def getConfigSections(cls):
+ "Only needs to be overriden if has additional ini sections."
+ return ['base_efiction',cls.getConfigSection()]
+
@classmethod
def getAcceptDomains(cls):
return [cls.getSiteDomain(),'www.' + cls.getSiteDomain()]
diff --git a/fanficfare/adapters/base_xenforoforum_adapter.py b/fanficfare/adapters/base_xenforoforum_adapter.py
new file mode 100644
index 0000000..f128e1a
--- /dev/null
+++ b/fanficfare/adapters/base_xenforoforum_adapter.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 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
+
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, makeDate
+
+logger = logging.getLogger(__name__)
+
+class BaseXenForoForumAdapter(BaseSiteAdapter):
+
+ def __init__(self, config, url):
+ BaseSiteAdapter.__init__(self, config, url)
+
+ self.decode = ["utf8",
+ "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.
+
+
+ # get storyId from url--url validation guarantees query is only sid=1234
+ self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2])
+
+ # 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.
+ self._setURL(self.getURLPrefix() + '/'+m.group('tp')+'/'+self.story.getMetadata('storyId')+'/')
+ else:
+ raise exceptions.InvalidStoryURL(url,
+ self.getSiteDomain(),
+ self.getSiteExampleURLs())
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','fsb')
+
+ # The date format will vary from site to site.
+ # http://docs.python.org/library/datetime.html#strftime-strptime-behavior
+ self.dateformat = "%b %d, %Y at %I:%M %p"
+
+ @classmethod
+ def getConfigSections(cls):
+ "Only needs to be overriden if has additional ini sections."
+ return ['base_xenforoforum',cls.getConfigSection()]
+
+ @classmethod
+ def getURLPrefix(cls):
+ # The site domain. Does have www here, if it uses it.
+ return 'https://' + cls.getSiteDomain()
+
+ @classmethod
+ def getSiteExampleURLs(cls):
+ return cls.getURLPrefix()+"/threads/some-story-name.123456/"
+
+ def getSiteURLPattern(self):
+ return r"https?://"+re.escape(self.getSiteDomain())+r"/(?P'+chaptertitle+u'
',u''+chapterorigtitle+u'
')
+
+ entrychanged = ( origdata != data )
+ changed = changed or entrychanged
+
+ if entrychanged:
+ ## go after the TOC entry, too.
+ #
${chapter}
@@ -502,13 +506,13 @@ div { margin: 0pt; padding: 0pt; }
items.append(("log_page","OEBPS/log_page.xhtml","application/xhtml+xml","Update Log"))
itemrefs.append("log_page")
- for index, (url,title,html) in enumerate(self.story.getChapters(fortoc=True)):
- if html:
+ for index, chap in enumerate(self.story.getChapters(fortoc=True)):
+ if chap.html:
i=index+1
items.append(("file%04d"%i,
"OEBPS/file%04d.xhtml"%i,
"application/xhtml+xml",
- title))
+ chap.title))
itemrefs.append("file%04d"%i)
manifest = contentdom.createElement("manifest")
@@ -650,19 +654,21 @@ div { margin: 0pt; padding: 0pt; }
else:
CHAPTER_END = self.EPUB_CHAPTER_END
- for index, (url,title,html) in enumerate(self.story.getChapters()):
- if html:
- logger.debug('Writing chapter text for: %s' % title)
- vals={'url':url, 'chapter':title, 'index':"%04d"%(index+1), 'number':index+1}
- fullhtml = CHAPTER_START.substitute(vals) + html + CHAPTER_END.substitute(vals)
+ for index, chap in enumerate(self.story.getChapters()): # (url,title,html)
+ if chap.html:
+ logger.debug('Writing chapter text for: %s' % chap.title)
+ vals={'url':removeEntities(chap.url),
+ 'chapter':chap.title,
+ 'origchapter':chap.origtitle,
+ 'tocchapter':chap.toctitle,
+ 'index':"%04d"%(index+1),
+ 'number':index+1}
+ fullhtml = CHAPTER_START.substitute(vals) + \
+ chap.html + CHAPTER_END.substitute(vals)
# ffnet(& maybe others) gives the whole chapter text
# as one line. This causes problems for nook(at
# least) when the chapter size starts getting big
# (200k+)
- #fullhtml = fullhtml.replace('
Questions? Check out our
diff --git a/webservice/main.py b/webservice/main.py
index b480265..4435312 100644
--- a/webservice/main.py
+++ b/webservice/main.py
@@ -62,7 +62,7 @@ class UserConfigServer(webapp2.RequestHandler):
def getUserConfig(self,user,url,fileformat):
- configuration = Configuration(adapters.getConfigSectionFor(url),fileformat)
+ configuration = Configuration(adapters.getConfigSectionsFor(url),fileformat)
logging.debug('reading defaults.ini config file')
configuration.read('fanficfare/defaults.ini')
@@ -366,6 +366,16 @@ class FanfictionDownloader(UserConfigServer):
self.redirect('/')
return
+ # Allow chapter range with URL.
+ # test1.com?sid=5[4-6]
+ mc = re.match(r"^(?P