Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-fanficfare for 
openSUSE:Factory checked in at 2022-11-22 16:10:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-fanficfare (Old)
 and      /work/SRC/openSUSE:Factory/.python-fanficfare.new.1597 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-fanficfare"

Tue Nov 22 16:10:41 2022 rev:45 rq:1037251 version:4.18.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-fanficfare/python-fanficfare.changes      
2022-10-25 11:20:29.182200339 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-fanficfare.new.1597/python-fanficfare.changes
    2022-11-22 16:10:48.958222689 +0100
@@ -1,0 +2,17 @@
+Tue Nov 22 07:50:20 UTC 2022 - Matej Cepl <mc...@suse.com>
+
+- Update to 4.18.0:
+  - Update metadata caching with dependency invalidating
+  - Still allow images with use_flaresolverr_proxy if
+    use_browser_cache
+  - Support classic AND modern (and minimalist) theme for
+    storiesonline, finestories and scifistories - thanks, mvlcek
+  - adapter_tenhawkpresents: Change site to t.evancurrie.ca -
+    tenhawk domain semi-broken
+  - remove_class_chapter missing from config lists
+  - adapter_adultfanfictionorg: Fixes for site changes, thanks
+    cryosaur.
+  - Remove Calibre Update Cover option entirely(was deprecated)
+    #878
+
+-------------------------------------------------------------------

Old:
----
  FanFicFare-4.17.0.tar.gz

New:
----
  FanFicFare-4.18.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-fanficfare.spec ++++++
--- /var/tmp/diff_new_pack.Soy2qb/_old  2022-11-22 16:10:49.534225612 +0100
+++ /var/tmp/diff_new_pack.Soy2qb/_new  2022-11-22 16:10:49.542225652 +0100
@@ -21,7 +21,7 @@
 %define skip_python2 1
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-fanficfare
-Version:        4.17.0
+Version:        4.18.0
 Release:        0
 Summary:        Tool for making eBooks from stories on fanfiction and other 
web sites
 License:        GPL-3.0-only

++++++ FanFicFare-4.17.0.tar.gz -> FanFicFare-4.18.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/__init__.py 
new/FanFicFare-4.18.0/calibre-plugin/__init__.py
--- old/FanFicFare-4.17.0/calibre-plugin/__init__.py    2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/__init__.py    2022-11-22 
02:04:53.000000000 +0100
@@ -33,7 +33,7 @@
 from calibre.customize import InterfaceActionBase
 
 # pulled out from FanFicFareBase for saving in prefs.py
-__version__ = (4, 17, 0)
+__version__ = (4, 18, 0)
 
 ## Apparently the name for this class doesn't matter--it was still
 ## 'demo' for the first few versions.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/config.py 
new/FanFicFare-4.18.0/calibre-plugin/config.py
--- old/FanFicFare-4.17.0/calibre-plugin/config.py      2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/config.py      2022-11-22 
02:04:53.000000000 +0100
@@ -70,8 +70,7 @@
 
 from calibre_plugins.fanficfare_plugin.dialogs import (
     UPDATE, UPDATEALWAYS, collision_order, save_collisions, RejectListDialog,
-    EditTextDialog, IniTextDialog, RejectUrlEntry,
-    updateepubcover_warning)
+    EditTextDialog, IniTextDialog, RejectUrlEntry)
 
 from fanficfare.adapters import getSiteSections, get_section_url
 
@@ -280,7 +279,6 @@
             prefs['collision'] = 
save_collisions[unicode(self.basic_tab.collision.currentText())]
             prefs['updatemeta'] = self.basic_tab.updatemeta.isChecked()
             prefs['bgmeta'] = self.basic_tab.bgmeta.isChecked()
-            prefs['updateepubcover'] = 
self.basic_tab.updateepubcover.isChecked()
             prefs['keeptags'] = self.basic_tab.keeptags.isChecked()
             prefs['mark'] = self.basic_tab.mark.isChecked()
             prefs['mark_success'] = self.basic_tab.mark_success.isChecked()
@@ -492,22 +490,6 @@
 
         self.l.addLayout(horz)
 
-        label = QLabel(_("The <i><b>Update EPUB Cover</b></i> option is 
DEPRECATED and in future will always be ON.<br>"
-                         "For now you can still uncheck it here, but please 
see <a href='https://github.com/JimmXinu/FanFicFare/issues/878'>this issue</a> 
for more information."))
-        label.setWordWrap(True)
-        label.setOpenExternalLinks(True)
-        self.l.addWidget(label)
-
-        self.updateepubcover = QCheckBox(_('Default Update EPUB Cover when 
Updating EPUB?'),self)
-        self.updateepubcover.setToolTip(_("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."))
-        self.updateepubcover.setChecked(prefs['updateepubcover'])
-        self.l.addWidget(self.updateepubcover)
-
-        def updateepubcover_changed():
-            if not self.updateepubcover.isChecked():
-                updateepubcover_warning()
-        self.updateepubcover.stateChanged.connect(updateepubcover_changed)
-
         cali_gb = groupbox = QGroupBox(_("Updating Calibre Options"))
         self.l = QVBoxLayout()
         groupbox.setLayout(self.l)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/dialogs.py 
new/FanFicFare-4.18.0/calibre-plugin/dialogs.py
--- old/FanFicFare-4.17.0/calibre-plugin/dialogs.py     2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/dialogs.py     2022-11-22 
02:04:53.000000000 +0100
@@ -314,14 +314,6 @@
         self.mergehide.append(self.updatemeta)
         self.mergeupdateshow.append(self.updatemeta)
 
-        self.updateepubcover = QCheckBox(_('Update EPUB Cover?'),self)
-        # self.updateepubcover.setToolTip(_('Update book cover image from site 
or defaults (if found) <i>inside</i> the EPUB when EPUB is updated.'))
-        self.updateepubcover.setToolTip(_('This feature is being removed.  See 
FanFicFare > Config > General.'))
-        self.updateepubcover.setChecked(self.prefs['updateepubcover'])
-        self.updateepubcover.setEnabled(False)
-        horz.addWidget(self.updateepubcover)
-        self.mergehide.append(self.updateepubcover)
-
         self.gbl.addLayout(horz)
 
         ## bgmeta not used with Add New because of stories that change
@@ -451,11 +443,6 @@
         self.updatemeta.setChecked(self.prefs['updatemeta'])
         # self.bgmeta.setChecked(self.prefs['bgmeta'])
 
-        if not self.merge:
-            self.updateepubcover.setChecked(self.prefs['updateepubcover'])
-            if not self.prefs['updateepubcover']:
-                updateepubcover_warning()
-
         self.url.setText(url_list_text)
         if url_list_text:
             self.button_box.button(QDialogButtonBox.Ok).setFocus()
@@ -493,14 +480,12 @@
             'collision': unicode(self.collision.currentText()),
             'updatemeta': self.updatemeta.isChecked(),
             'bgmeta': False, # self.bgmeta.isChecked(),
-            'updateepubcover': self.updateepubcover.isChecked(),
             'smarten_punctuation':self.prefs['smarten_punctuation'],
             'do_wordcount':self.prefs['do_wordcount'],
             }
 
         if self.merge:
             retval['fileform']=='epub'
-            retval['updateepubcover']=True
             if self.newmerge:
                 retval['updatemeta']=True
                 retval['collision']=ADDNEW
@@ -912,15 +897,6 @@
         self.updatemeta.setChecked(self.prefs['updatemeta'])
         horz.addWidget(self.updatemeta)
 
-        self.updateepubcover = QCheckBox(_('Update EPUB Cover?'),self)
-        # self.updateepubcover.setToolTip(_('Update book cover image from site 
or defaults (if found) <i>inside</i> the EPUB when EPUB is updated.'))
-        self.updateepubcover.setToolTip(_('This feature is being removed.  See 
FanFicFare > Config > General.'))
-        self.updateepubcover.setEnabled(False)
-        self.updateepubcover.setChecked(self.prefs['updateepubcover'])
-        horz.addWidget(self.updateepubcover)
-        if not self.prefs['updateepubcover']:
-            updateepubcover_warning()
-
         self.bgmeta = QCheckBox(_('Background Metadata?'),self)
         self.bgmeta.setToolTip(_("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."))
         self.bgmeta.setChecked(self.prefs['bgmeta'])
@@ -972,7 +948,6 @@
             'collision': unicode(self.collision.currentText()),
             'updatemeta': self.updatemeta.isChecked(),
             'bgmeta': self.bgmeta.isChecked(),
-            'updateepubcover': self.updateepubcover.isChecked(),
             'smarten_punctuation':self.prefs['smarten_punctuation'],
             'do_wordcount':self.prefs['do_wordcount'],
             }
@@ -1713,12 +1688,3 @@
         gprefs.set('questions_to_auto_skip', list(auto_skip))
 
     return ret
-
-from calibre.gui2.ui import get_gui
-def updateepubcover_warning():
-    return confirm('<p>'+_("FanFicFare's <i><b>Update EPUB Cover</b></i> 
Download Option is being removed.")+'<\p>'+
-                   '<p>'+_("It was a very old setting that didn't quite do 
what users expected.")+'<\p>'+
-                   '<p>'+_("You are getting this warning because you have 
<i><b>Default Update EPUB Cover when Updating EPUB?</b></i> unchecked in 
FanFicFare > Config > General.")+'<\p>'+
-                   '<p>'+_("See <a 
href='https://github.com/JimmXinu/FanFicFare/issues/878'>this issue</a> for 
more information.")+'<\p>',
-                   'fff_updateepubcover_warning',
-                   get_gui(), show_cancel_button=False, title=_("FanFicFare 
Warning"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/fff_plugin.py 
new/FanFicFare-4.18.0/calibre-plugin/fff_plugin.py
--- old/FanFicFare-4.17.0/calibre-plugin/fff_plugin.py  2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/fff_plugin.py  2022-11-22 
02:04:53.000000000 +0100
@@ -113,8 +113,7 @@
     LoopProgressDialog, UserPassDialog, AboutDialog, CollectURLDialog,
     RejectListDialog, EmailPassDialog,
     save_collisions, question_dialog_all,
-    NotGoingToDownload, RejectUrlEntry,
-    updateepubcover_warning)
+    NotGoingToDownload, RejectUrlEntry)
 
 # because calibre immediately transforms html into zip and don't want
 # to have an 'if html'.  db.has_format is cool with the case mismatch,
@@ -591,15 +590,12 @@
         if prefs['download_from_email_immediately']:
             ## do imap fetch w/o GUI elements
             if url_list:
-                if prefs['updateepubcover'] == False:
-                    updateepubcover_warning()
                 self.prep_downloads({
                         'fileform': prefs['fileform'],
                         # save_collisions==convert from save value to local 
lang value
                         'collision': 
extraoptions.get('collision',save_collisions[prefs['collision']]),
                         'updatemeta': prefs['updatemeta'],
                         'bgmeta': False,
-                        'updateepubcover': prefs['updateepubcover'],
                         'smarten_punctuation':prefs['smarten_punctuation'],
                         'do_wordcount':prefs['do_wordcount'],
                         'add_tag':prefs['imaptags'],
@@ -1237,8 +1233,7 @@
                            options={'fileform':'epub',
                                     'collision':ADDNEW,
                                     'updatemeta':True,
-                                    'bgmeta':False,
-                                    'updateepubcover':True},
+                                    'bgmeta':False},
                            merge=False):
         '''
         Update passed in book dict with metadata from website and
@@ -1260,7 +1255,6 @@
         collision = book['collision'] = options['collision']
         updatemeta= options['updatemeta']
         bgmeta= options['bgmeta']
-        updateepubcover= options['updateepubcover']
 
         ## Check reject list.  Redundant with below for when story URL
         ## changes, but also kept here to avoid network hit in most
@@ -1668,8 +1662,7 @@
                             options={'fileform':'epub',
                                      'collision':ADDNEW,
                                      'updatemeta':True,
-                                     'bgmeta':False,
-                                     'updateepubcover':True},
+                                     'bgmeta':False},
                             merge=False):
         '''
         Called by LoopProgressDialog to start story downloads BG processing.
@@ -1798,8 +1791,7 @@
                           options={'fileform':'epub',
                                    'collision':ADDNEW,
                                    'updatemeta':True,
-                                   'bgmeta':False,
-                                   'updateepubcover':True},
+                                   'bgmeta':False},
                           errorcol_label=None,
                           lastcheckedcol_label=None):
 
@@ -2149,7 +2141,6 @@
         lastcheckedcol_label = 
self.get_custom_col_label(prefs['lastcheckedcol'])
         if prefs['mark'] or errorcol_label or lastcheckedcol_label:
             self.previous = self.gui.library_view.currentIndex() # used by 
update_books_finish.
-            self.gui.status_bar.show_message(_('Adding/Updating %s BAD 
books.')%len(book_list))
             LoopProgressDialog(self.gui,
                                book_list,
                                partial(self.update_error_column_loop, 
db=self.gui.current_db, errorcol_label=errorcol_label, 
lastcheckedcol_label=lastcheckedcol_label),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/jobs.py 
new/FanFicFare-4.18.0/calibre-plugin/jobs.py
--- old/FanFicFare-4.17.0/calibre-plugin/jobs.py        2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/jobs.py        2022-11-22 
02:04:53.000000000 +0100
@@ -222,9 +222,6 @@
                                             options['fileform'],
                                             options['personal.ini'])
 
-            if not options['updateepubcover'] and 'epub_for_update' in book 
and book['collision'] in (UPDATE, UPDATEALWAYS):
-                configuration.set("overrides","never_make_cover","true")
-
             # images only for epub, html, even if the user mistakenly
             # turned it on else where.
             if options['fileform'] not in ("epub","html"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/plugin-defaults.ini 
new/FanFicFare-4.18.0/calibre-plugin/plugin-defaults.ini
--- old/FanFicFare-4.17.0/calibre-plugin/plugin-defaults.ini    2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/plugin-defaults.ini    2022-11-22 
02:04:53.000000000 +0100
@@ -143,6 +143,16 @@
 ## confidence required to use the chardet detected.
 #chardet_confidence_limit:0.9
 
+## Normally, try to make the filenames 'safe' by removing invalid
+## filename chars.  Applies to default_cover_image, force_cover_image,
+## output_filename & zip_filename.
+allow_unsafe_filename: false
+
+## The regex pattern of 'unsafe' filename chars for above.  First
+## character . OR any one or more characters that are NOT a letter,
+## number, or one of _. []()&'-
+output_filename_safepattern:(^\.|/\.|[^a-zA-Z0-9_\. \[\]\(\)&'-]+)
+
 ## entries to make epub subjects and calibre tags
 ## lastupdate creates two tags: "Last Update Year/Month: %Y/%m" and "Last 
Update: %Y/%m/%d"
 include_subject_tags: extratags, genre, category, characters, ships, status
@@ -1182,7 +1192,7 @@
 ## default_cover_image is a python string Template string with
 ## ${title}, ${author} etc, same as titlepage_entries. Unless
 ## allow_unsafe_filename is true, invalid filename chars will be
-## removed from metadata fields
+## removed from metadata fields and spaces are allowed.
 #default_cover_image:file:///C:/Users/username/Desktop/nook/images/icon.png
 
#default_cover_image:file:///C:/Users/username/Desktop/nook/images/${title}/icon.png
 #default_cover_image:http://www.somesite.com/someimage.gif
@@ -1194,7 +1204,7 @@
 ## force_cover_image is a python string Template string with
 ## ${title}, ${author} etc, same as titlepage_entries. Unless
 ## allow_unsafe_filename is true, invalid filename chars will be
-## removed from metadata fields
+## removed from metadata fields and spaces are allowed.
 #force_cover_image:file:///C:/Users/username/Desktop/nook/images/icon.png
 
#force_cover_image:file:///C:/Users/username/Desktop/nook/images/${title}/icon.png
 #force_cover_image:http://www.somesite.com/someimage.gif
@@ -1761,17 +1771,6 @@
 
 website_encodings:Windows-1252,utf8
 
-[fanfiction.tenhawkpresents.ink]
-use_basic_cache: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
-
-website_encodings:Windows-1252,utf8
-
 [fanficauthors.net]
 use_basic_cache:true
 ## Some sites also require the user to confirm they are adult for
@@ -2696,6 +2695,18 @@
 ## for those not expecting it.
 #append_datepublished_to_storyurl:false
 
+[t.evancurrie.ca]
+# was fanfiction.tenhawkpresents.ink
+use_basic_cache: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
+
+website_encodings:Windows-1252,utf8
+
 [tgstorytime.com]
 ## Site dedicated to these categories/characters/ships
 extracategories:Transgender
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/calibre-plugin/prefs.py 
new/FanFicFare-4.18.0/calibre-plugin/prefs.py
--- old/FanFicFare-4.17.0/calibre-plugin/prefs.py       2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/calibre-plugin/prefs.py       2022-11-22 
02:04:53.000000000 +0100
@@ -120,7 +120,7 @@
 
 default_prefs['updatemeta'] = True
 default_prefs['bgmeta'] = False
-default_prefs['updateepubcover'] = True # removed in favor of always True Sep 
2022
+#default_prefs['updateepubcover'] = True # removed in favor of always True Oct 
2022
 default_prefs['keeptags'] = False
 default_prefs['suppressauthorsort'] = False
 default_prefs['suppresstitlesort'] = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/FanFicFare-4.17.0/fanficfare/adapters/adapter_adultfanfictionorg.py 
new/FanFicFare-4.18.0/fanficfare/adapters/adapter_adultfanfictionorg.py
--- old/FanFicFare-4.17.0/fanficfare/adapters/adapter_adultfanfictionorg.py     
2022-10-18 18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/adapters/adapter_adultfanfictionorg.py     
2022-11-22 02:04:53.000000000 +0100
@@ -217,7 +217,7 @@
         self.story.setMetadata('title',stripHTML(a).replace('\\','').replace(' 
 ',' ').replace('  ',' ').replace('  ',' ').strip())
 
         # Find the chapters:
-        chapters = soup.find('div',{'class':'dropdown-content'})
+        chapters = soup.find('ul',{'class':'dropdown-content'})
         for i, chapter in enumerate(chapters.findAll('a')):
             self.add_chapter(chapter,self.url+'&chapter='+unicode(i+1))
 
@@ -373,7 +373,7 @@
         logger.debug('Getting chapter text from: %s' % url)
 
         soup = self.make_soup(self.get_request(url))
-        chaptertag = soup.find('div',{'class' : 
'pagination'}).parent.findNext('td')
+        chaptertag = 
soup.find('ul',{'class':'pagination'}).parent.parent.parent.findNextSibling('li')
         if None == chaptertag:
             raise exceptions.FailedToDownload("Error downloading Chapter: {0}! 
 Missing required element!".format(url))
         # Change td to a div.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/FanFicFare-4.17.0/fanficfare/adapters/adapter_finestoriescom.py 
new/FanFicFare-4.18.0/fanficfare/adapters/adapter_finestoriescom.py
--- old/FanFicFare-4.17.0/fanficfare/adapters/adapter_finestoriescom.py 
2022-10-18 18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/adapters/adapter_finestoriescom.py 
2022-11-22 02:04:53.000000000 +0100
@@ -38,29 +38,3 @@
     def getSiteDomain():
         # The site domain.  Does have www here, if it uses it.
         return 'finestories.com'
-
-    @classmethod
-    def getTheme(cls):
-        ## only one theme is supported.
-        return "Modern"
-
-    ## Login seems to be reasonably standard across eFiction sites.
-    def needToLoginCheck(self, data):
-        if 'Free Registration' in data \
-                or "Log In" in data \
-                or "Invalid Password!" in data \
-                or "Invalid User Name!" in data:
-            return True
-        else:
-            return False
-
-    def getStoryMetadataFromAuthorPage(self):
-        # surprisingly, the detailed page does not give enough details, so go 
to author's page
-        story_row = self.findStoryRow('div')
-
-        description_element = story_row.find('div', {'class' : 'sdesc'})
-
-        self.parseDescriptionField(description_element)
-
-        misc_element = story_row.find('div', {'class' : 'misc'})
-        self.parseOtherAttributes(misc_element)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/FanFicFare-4.17.0/fanficfare/adapters/adapter_storiesonlinenet.py 
new/FanFicFare-4.18.0/fanficfare/adapters/adapter_storiesonlinenet.py
--- old/FanFicFare-4.17.0/fanficfare/adapters/adapter_storiesonlinenet.py       
2022-10-18 18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/adapters/adapter_storiesonlinenet.py       
2022-11-22 02:04:53.000000000 +0100
@@ -91,7 +91,7 @@
 
     @classmethod
     def getTheme(cls):
-        ## only one theme is supported.
+        # preferred theme
         return "Classic"
 
     def needToLoginCheck(self, data):
@@ -267,29 +267,40 @@
 
     def getStoryMetadataFromAuthorPage(self):
         # surprisingly, the detailed page does not give enough details, so go 
to author's page
-        story_row = self.findStoryRow('tr')
-        self.has_universes = False
+        story_row = self.findStoryRow()
 
-        title_cell = story_row.find('td', {'class' : 'lc2'})
-        for cat in title_cell.findAll('div', {'class' : 'typediv'}):
-            self.story.addToList('genre',cat.text)
+        if story_row.name == 'tr':
+            # classic theme
+            self.has_universes = False
 
-        # in lieu of word count.
-        self.story.setMetadata('size', story_row.find('td', {'class' : 
'num'}).text)
+            title_cell = story_row.find('td', {'class' : 'lc2'})
+            for cat in title_cell.findAll('div', {'class' : 'typediv'}):
+                self.story.addToList('genre',cat.text)
 
-        score = story_row.findNext('th', {'class' : 'ynum'}).text
-        if re.match(r"[\d,\.]+",score):
-            self.story.setMetadata('score', score)
+            # in lieu of word count.
+            self.story.setMetadata('size', story_row.find('td', {'class' : 
'num'}).text)
 
-        description_element = story_row.findNext('td', {'class' : 'lc4'})
-        # logger.debug(description_element)
+            score = story_row.findNext('th', {'class' : 'ynum'}).text
+            if re.match(r"[\d,\.]+",score):
+                self.story.setMetadata('score', score)
 
-        self.parseDescriptionField(description_element)
+            description_element = story_row.findNext('td', {'class' : 'lc4'})
+            # logger.debug(description_element)
 
-        self.parseOtherAttributes(description_element)
+            self.parseDescriptionField(description_element)
+
+            self.parseOtherAttributes(description_element)
+        else:
+            # modern theme (or minimalist theme, should also work)
+            description_element = story_row.find('div', {'class' : 'sdesc'})
+
+            self.parseDescriptionField(description_element)
+
+            misc_element = story_row.find('div', {'class' : 'misc'})
+            self.parseOtherAttributes(misc_element)
 
 
-    def findStoryRow(self, row_class='tr'):
+    def findStoryRow(self):
         page=0
         story_found = False
         while not story_found:
@@ -301,7 +312,14 @@
                     raise exceptions.FailedToDownload("Story not found in 
Author's list--Set Access Level to Full Access and change Listings Theme back 
to "+self.getTheme())
             asoup = self.make_soup(data)
 
-            story_row = asoup.find(row_class, {'id' : 'sr' + 
self.story.getMetadata('storyId')})
+            story_row = asoup.find('tr', {'id' : 'sr' + 
self.story.getMetadata('storyId')})
+            if story_row:
+                logger.debug("Found story row on page %d" % page)
+                story_found = True
+                self.has_universes = "/universes" in data
+                break
+
+            story_row = asoup.find('div', {'id' : 'sr' + 
self.story.getMetadata('storyId')})
             if story_row:
                 logger.debug("Found story row on page %d" % page)
                 story_found = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/FanFicFare-4.17.0/fanficfare/adapters/adapter_tenhawkpresents.py 
new/FanFicFare-4.18.0/fanficfare/adapters/adapter_tenhawkpresents.py
--- old/FanFicFare-4.17.0/fanficfare/adapters/adapter_tenhawkpresents.py        
2022-10-18 18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/adapters/adapter_tenhawkpresents.py        
2022-11-22 02:04:53.000000000 +0100
@@ -48,7 +48,7 @@
 
     @staticmethod
     def getSiteDomain():
-        return 'fanfiction.tenhawkpresents.ink'
+        return 't.evancurrie.ca'
 
     @classmethod
     def getSiteExampleURLs(cls):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/fanficfare/cli.py 
new/FanFicFare-4.18.0/fanficfare/cli.py
--- old/FanFicFare-4.17.0/fanficfare/cli.py     2022-10-18 18:47:27.000000000 
+0200
+++ new/FanFicFare-4.18.0/fanficfare/cli.py     2022-11-22 02:04:53.000000000 
+0100
@@ -28,7 +28,7 @@
 import os, sys, platform
 
 
-version="4.17.0"
+version="4.18.0"
 os.environ['CURRENT_VERSION_ID']=version
 
 global_cache = 'global_cache'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/fanficfare/configurable.py 
new/FanFicFare-4.18.0/fanficfare/configurable.py
--- old/FanFicFare-4.17.0/fanficfare/configurable.py    2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/configurable.py    2022-11-22 
02:04:53.000000000 +0100
@@ -192,6 +192,7 @@
                'replace_hr':(None,None,boollist),
                'sort_ships':(None,None,boollist),
                'strip_chapter_numbers':(None,None,boollist),
+               'remove_class_chapter':(None,None,boollist),
                'mark_new_chapters':(None,None,boollist+['latestonly']),
                'titlepage_use_table':(None,None,boollist),
 
@@ -425,6 +426,7 @@
                  'keep_style_attr',
                  'keep_title_attr',
                  'keep_html_attrs',
+                 'remove_class_chapter',
                  'replace_tags_with_spans',
                  'keep_empty_tags',
                  'remove_tags',
@@ -458,6 +460,7 @@
                  'rating_titles',
                  'remove_transparency',
                  'replace_br_with_p',
+                 'replace_chapter_text',
                  'replace_hr',
                  'replace_xbr_with_hr',
                  'replace_metadata',
@@ -900,7 +903,8 @@
         clude_metadata_re = 
re.compile(r'(add_to_)?(in|ex)clude_metadata_(pre|post)$')
 
         replace_metadata_re = re.compile(r'(add_to_)?replace_metadata$')
-        from .story import set_in_ex_clude, make_replacements
+        replace_chapter_text_re = 
re.compile(r'(add_to_)?replace_chapter_text$')
+        from .story import set_in_ex_clude, make_replacements, 
make_chapter_text_replacements
 
         custom_columns_settings_re = 
re.compile(r'(add_to_)?custom_columns_settings$')
         custom_columns_flags_re = re.compile(r'^[rna](_anthaver)?')
@@ -939,6 +943,9 @@
                         if replace_metadata_re.match(keyword):
                             make_replacements(value)
 
+                        if replace_chapter_text_re.match(keyword):
+                            make_chapter_text_replacements(value)
+
                         if generate_cover_settings_re.match(keyword):
                             make_generate_cover_settings(value)
 
@@ -1023,7 +1030,7 @@
             if self.getConfig('use_flaresolverr_proxy',False):
                 
logger.debug("use_flaresolverr_proxy:%s"%self.getConfig('use_flaresolverr_proxy'))
                 fetchcls = flaresolverr_proxy.FlareSolverr_ProxyFetcher
-                if self.getConfig('use_flaresolverr_proxy') != 'withimages':
+                if self.getConfig('use_flaresolverr_proxy') != 'withimages' 
and not self.getConfig('use_browser_cache'):
                     logger.warning("FlareSolverr v2+ doesn't work with images: 
include_images automatically set false")
                     logger.warning("Set use_flaresolverr_proxy:withimages if 
your are using FlareSolver v1 and want images")
                     self.set('overrides', 'include_images', 'false')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/fanficfare/defaults.ini 
new/FanFicFare-4.18.0/fanficfare/defaults.ini
--- old/FanFicFare-4.17.0/fanficfare/defaults.ini       2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/defaults.ini       2022-11-22 
02:04:53.000000000 +0100
@@ -164,8 +164,8 @@
 zip_filename: ${title}-${siteabbrev}_${storyId}${formatext}.zip
 
 ## Normally, try to make the filenames 'safe' by removing invalid
-## filename chars.  Applies to default_cover_image, output_filename &
-## zip_filename.
+## filename chars.  Applies to default_cover_image, force_cover_image,
+## output_filename & zip_filename.
 allow_unsafe_filename: false
 
 ## The regex pattern of 'unsafe' filename chars for above.  First
@@ -1782,17 +1782,6 @@
 
 website_encodings:Windows-1252,utf8
 
-[fanfiction.tenhawkpresents.ink]
-use_basic_cache: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
-
-website_encodings:Windows-1252,utf8
-
 [fanficauthors.net]
 use_basic_cache:true
 ## Some sites also require the user to confirm they are adult for
@@ -2717,6 +2706,18 @@
 ## for those not expecting it.
 #append_datepublished_to_storyurl:false
 
+[t.evancurrie.ca]
+# was fanfiction.tenhawkpresents.ink
+use_basic_cache: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
+
+website_encodings:Windows-1252,utf8
+
 [tgstorytime.com]
 ## Site dedicated to these categories/characters/ships
 extracategories:Transgender
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/fanficfare/exceptions.py 
new/FanFicFare-4.18.0/fanficfare/exceptions.py
--- old/FanFicFare-4.17.0/fanficfare/exceptions.py      2022-10-18 
18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/fanficfare/exceptions.py      2022-11-22 
02:04:53.000000000 +0100
@@ -114,6 +114,13 @@
     def __str__(self):
         return self.error
 
+class CacheCleared(Exception):
+    def __init__(self,error):
+        self.error=error
+
+    def __str__(self):
+        return self.error
+
 class HTTPErrorFFF(Exception):
     def __init__(self,
                  url,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/fanficfare/story.py 
new/FanFicFare-4.18.0/fanficfare/story.py
--- old/FanFicFare-4.17.0/fanficfare/story.py   2022-10-18 18:47:27.000000000 
+0200
+++ new/FanFicFare-4.18.0/fanficfare/story.py   2022-11-22 02:04:53.000000000 
+0100
@@ -454,6 +454,32 @@
     # print("replace lines:%s"%len(retval))
     return retval
 
+def make_chapter_text_replacements(replace):
+    retval=[]
+    for repl_line in replace.splitlines():
+        line=repl_line
+        try:
+            (regexp,replacement)=(None,None)
+            if "=>" in line:
+                parts = line.split("=>")
+                (regexp,replacement)=parts
+
+            if regexp:
+                regexp = re_compile(regexp,line)
+                # A way to explicitly include spaces in the
+                # replacement string.  The .ini parser eats any
+                # trailing spaces.
+                replacement=replacement\
+                    .replace(SPACE_REPLACE,' ')
+
+                retval.append([repl_line,regexp,replacement])
+        except Exception as e:
+            logger.error("Problem with Chapter Text Replacement 
Line:%s"%repl_line)
+            raise exceptions.PersonalIniFailed(e,'replace_chapter_text 
unpacking failed',repl_line)
+#            raise
+    # print("replace lines:%s"%len(retval))
+    return retval
+
 class StoryImage(dict):
     pass
 
@@ -512,6 +538,83 @@
         # logger.debug(self.size_index.keys())
         # logger.debug("\n"+("\n".join([ x['newsrc'] for x in self.infos])))
 
+
+class MetadataCache:
+    def __init__(self):
+        # save processed metadata, dicts keyed by 'key', then 
(removeentities,dorepl)
+        # {'key':{(removeentities,dorepl):"value",(...):"value"},'key':... }
+        self.processed_metadata_cache = {}
+        ## not entirely sure now why lists are separate, but I assume
+        ## there was a reason.
+        self.processed_metadata_list_cache = {}
+
+        ## lists of entries that depend on key value--IE, the ones
+        ## that should also be cache invalided when key is.
+        # {'key':['name','name',...]
+        self.dependent_entries = {}
+
+    def clear(self):
+        self.processed_metadata_cache = {}
+        self.processed_metadata_list_cache = {}
+
+    def invalidate(self,key,seen_list={}):
+        # logger.debug("invalidate(%s)"%key)
+        # logger.debug("seen_list(%s)"%seen_list)
+        if key in seen_list:
+            raise exceptions.CacheCleared('replace all')
+        try:
+            new_seen_list = dict(seen_list)
+            new_seen_list[key]=True
+            if key in self.processed_metadata_cache:
+                del self.processed_metadata_cache[key]
+            if key in self.processed_metadata_list_cache:
+                del self.processed_metadata_list_cache[key]
+
+            for entry in self.dependent_entries.get(key,[]):
+                ## replace_metadata lines without keys apply to all
+                ## entries--special key '' used to clear deps on *all*
+                ## cache sets.
+                if entry == '':
+                    # logger.debug("clear in invalidate(%s)"%key)
+                    raise exceptions.CacheCleared('recursed')
+                self.invalidate(entry,new_seen_list)
+        except exceptions.CacheCleared as e:
+            # logger.debug(e)
+            self.clear()
+        # logger.debug(self.dependent_entries)
+
+    def add_dependencies(self,include_key,list_keys):
+        for key in list_keys:
+            if key not in self.dependent_entries:
+                self.dependent_entries[key] = set()
+            self.dependent_entries[key].add(include_key)
+
+    def set_cached_scalar(self,key,removeallentities,doreplacements,value):
+        if key not in self.processed_metadata_cache:
+            self.processed_metadata_cache[key] = {}
+        self.processed_metadata_cache[key][(removeallentities,doreplacements)] 
= value
+
+    def is_cached_scalar(self,key,removeallentities,doreplacements):
+        return key in self.processed_metadata_cache \
+            and (removeallentities,doreplacements) in 
self.processed_metadata_cache[key]
+
+    def get_cached_scalar(self,key,removeallentities,doreplacements):
+        return 
self.processed_metadata_cache[key][(removeallentities,doreplacements)]
+
+
+    def set_cached_list(self,key,removeallentities,doreplacements,value):
+        if key not in self.processed_metadata_list_cache:
+            self.processed_metadata_list_cache[key] = {}
+        
self.processed_metadata_list_cache[key][(removeallentities,doreplacements)] = 
value
+
+    def is_cached_list(self,key,removeallentities,doreplacements):
+        return key in self.processed_metadata_list_cache \
+            and (removeallentities,doreplacements) in 
self.processed_metadata_list_cache[key]
+
+    def get_cached_list(self,key,removeallentities,doreplacements):
+        return 
self.processed_metadata_list_cache[key][(removeallentities,doreplacements)]
+
+
 class Story(Requestable):
 
     def __init__(self, configuration):
@@ -523,6 +626,7 @@
             self.metadata = {'version':'unknown'}
         self.metadata['python_version']=sys.version
         self.replacements = []
+        self.chapter_text_replacements = []
         self.in_ex_cludes = {}
         self.chapters = [] # chapters will be dict 
containing(url,title,html,etc)
         self.chapter_first = None
@@ -530,10 +634,13 @@
 
         self.img_store = ImageStore()
 
-        # save processed metadata, dicts keyed by 'key', then 
(removeentities,dorepl)
-        # {'key':{(removeentities,dorepl):"value",(...):"value"},'key':... }
-        self.processed_metadata_cache = {}
-        self.processed_metadata_list_cache = {}
+        self.metadata_cache = MetadataCache()
+
+        ## set include_in_ cache dependencies
+        for entry in self.getValidMetaList():
+            if self.hasConfig("include_in_"+entry):
+                self.metadata_cache.add_dependencies(entry,
+                  [ k.replace('.NOREPL','') for k in 
self.getConfigList("include_in_"+entry) ])
 
         self.cover=None # *href* of new cover image--need to create html.
         self.oldcover=None # 
(oldcoverhtmlhref,oldcoverhtmltype,oldcoverhtmldata,oldcoverimghref,oldcoverimgtype,oldcoverimgdata)
@@ -541,6 +648,7 @@
         self.logfile=None # cheesy way to carry log file forward across update.
 
         self.replacements_prepped = False
+        self.chapter_text_replacements_prepped = False
 
         self.chapter_error_count = 0
 
@@ -560,6 +668,19 @@
 
             self.replacements =  
make_replacements(self.getConfig('replace_metadata'))
 
+            ## set replace_metadata conditional key cache dependencies
+            for replaceline in self.replacements:
+                (repl_line,metakeys,regexp,replacement,cond_match) = 
replaceline
+                ## replace_metadata lines without keys apply to all
+                ## entries--special key '' used to clear deps on *all*
+                ## cache sets.
+                if not metakeys:
+                    metakeys = ['']
+                for key in metakeys:
+                    if cond_match:
+                        
self.metadata_cache.add_dependencies(key.replace('_LIST',''),
+                                                             [ 
cond_match.key() ])
+
             in_ex_clude_list = ['include_metadata_pre','exclude_metadata_pre',
                                 
'include_metadata_post','exclude_metadata_post']
             for ie in in_ex_clude_list:
@@ -570,9 +691,15 @@
                     self.in_ex_cludes[ie] = set_in_ex_clude(ies)
             self.replacements_prepped = True
 
+            for which in self.in_ex_cludes.values():
+                for (line,match,cond_match) in which:
+                    for key in match.keys:
+                        if cond_match:
+                            
self.metadata_cache.add_dependencies(key.replace('_LIST',''),
+                                                                 [ 
cond_match.key() ])
+
     def clear_processed_metadata_cache(self):
-        self.processed_metadata_cache = {}
-        self.processed_metadata_list_cache = {}
+        self.metadata_cache.clear()
 
     def set_chapters_range(self,first=None,last=None):
         self.chapter_first=first
@@ -584,8 +711,8 @@
     def setMetadata(self, key, value, condremoveentities=True):
 
         # delete cached replace'd value.
-        if key in self.processed_metadata_cache:
-            del self.processed_metadata_cache[key]
+        self.metadata_cache.invalidate(key)
+
         # Fixing everything downstream to handle bool primatives is a
         # pain.
         if isinstance(value,bool):
@@ -683,7 +810,7 @@
                 # huuuge replace list cause a problem.  Also allows dict()
                 # instead of list() for quicker lookups.
                 if repl_line in seen_list:
-                    logger.info("Skipping replace_metadata line %s to prevent 
infinite recursion."%repl_line)
+                    logger.info("Skipping replace_metadata line '%s' on %s to 
prevent infinite recursion."%(repl_line,key))
                     continue
                 doreplace=True
                 if cond_match and cond_match.key() != key: # prevent infinite 
recursion.
@@ -824,9 +951,8 @@
                     doreplacements=True,
                     seen_list={}):
         # check for a cached value to speed processing
-        if key in self.processed_metadata_cache \
-                and (removeallentities,doreplacements) in 
self.processed_metadata_cache[key]:
-            return 
self.processed_metadata_cache[key][(removeallentities,doreplacements)]
+        if 
self.metadata_cache.is_cached_scalar(key,removeallentities,doreplacements):
+            return 
self.metadata_cache.get_cached_scalar(key,removeallentities,doreplacements)
 
         value = None
         if not self.isValidMetaEntry(key):
@@ -870,9 +996,7 @@
             value = self.getConfig("default_value_"+key)
 
         # save a cached value to speed processing
-        if key not in self.processed_metadata_cache:
-            self.processed_metadata_cache[key] = {}
-        self.processed_metadata_cache[key][(removeallentities,doreplacements)] 
= value
+        
self.metadata_cache.set_cached_scalar(key,removeallentities,doreplacements,value)
 
         return value
 
@@ -983,8 +1107,9 @@
             self.addToList(listname,v.strip())
 
     def addToList(self,listname,value,condremoveentities=True,clear=False):
-        if listname in self.processed_metadata_list_cache:
-            del self.processed_metadata_list_cache[listname]
+        # delete cached replace'd value.
+        self.metadata_cache.invalidate(listname)
+
         if value==None:
             return
         if condremoveentities:
@@ -1012,9 +1137,8 @@
         retlist = []
 
         # check for a cached value to speed processing
-        if not skip_cache and listname in self.processed_metadata_list_cache \
-                and (removeallentities,doreplacements) in 
self.processed_metadata_list_cache[listname]:
-            return 
self.processed_metadata_list_cache[listname][(removeallentities,doreplacements)]
+        if not skip_cache and 
self.metadata_cache.is_cached_list(listname,removeallentities,doreplacements):
+            return 
self.metadata_cache.get_cached_list(listname,removeallentities,doreplacements)
 
         if not self.isValidMetaEntry(listname):
             retlist = []
@@ -1135,9 +1259,7 @@
                 retlist = []
 
         if not skip_cache:
-            if listname not in self.processed_metadata_list_cache:
-                self.processed_metadata_list_cache[listname] = {}
-            
self.processed_metadata_list_cache[listname][(removeallentities,doreplacements)]
 = retlist
+            
self.metadata_cache.set_cached_list(listname,removeallentities,doreplacements,retlist)
 
         return retlist
 
@@ -1243,11 +1365,26 @@
             chapter['toctitle'] = toctempl.substitute(chapter)
             # set after, otherwise changes origtitle and toctitle
             chapter['title'] = chapter['chapter']
-            ## XXX -- add chapter text replacement here?
-            ## chapter['html'] is a soup or soup part?
+            ## chapter['html'] is a string.
+            chapter['html'] = 
self.do_chapter_text_replacements(chapter['html'])
             retval.append(chapter)
         return retval
 
+    def do_chapter_text_replacements(self,data):
+        '''
+        'Undocumented' feature.  This is a shotgun with a stirrup on
+        the end--you *will* shoot yourself in the foot a lot with it.
+        '''
+        # only compile chapter_text_replacements once.
+        if not self.chapter_text_replacements and 
self.getConfig('replace_chapter_text'):
+            self.chapter_text_replacements = 
make_chapter_text_replacements(self.getConfig('replace_chapter_text'))
+            logger.debug(self.chapter_text_replacements)
+        for replaceline in self.chapter_text_replacements:
+            (repl_line,regexp,replacement) = replaceline
+            if regexp.search(data):
+                data = regexp.sub(replacement,data)
+        return data
+
     def get_filename_safe_metadata(self,pattern=None):
         origvalues = self.getAllMetadata()
         values={}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FanFicFare-4.17.0/setup.py 
new/FanFicFare-4.18.0/setup.py
--- old/FanFicFare-4.17.0/setup.py      2022-10-18 18:47:27.000000000 +0200
+++ new/FanFicFare-4.18.0/setup.py      2022-11-22 02:04:53.000000000 +0100
@@ -26,7 +26,7 @@
     name=package_name,
 
     # Versions should comply with PEP440.
-    version="4.17.0",
+    version="4.18.0",
 
     description='A tool for downloading fanfiction to eBook formats',
     long_description=long_description,

Reply via email to