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,