mirror of
https://github.com/wassname/FanFicFare.git
synced 2026-09-15 11:36:30 +08:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6875d472d | ||
|
|
5758e364e4 | ||
|
|
d9a99fb7e9 | ||
|
|
0a32cdb277 | ||
|
|
00c1fe8704 | ||
|
|
bb96ecd5fc | ||
|
|
64866f7da6 | ||
|
|
73815bda43 | ||
|
|
d54acd936e | ||
|
|
e88244bebd | ||
|
|
02bdaf1086 | ||
|
|
21d5a39958 | ||
|
|
db661b1f9d | ||
|
|
0f654e86d6 | ||
|
|
063ee09e36 | ||
|
|
4bb26278ad | ||
|
|
d1f11f8ac4 | ||
|
|
ec352e728e | ||
|
|
e4e5e4a47a | ||
|
|
8632cfdbd6 | ||
|
|
c943ce7fce | ||
|
|
7ccddd5a07 | ||
|
|
3678bf6bf1 | ||
|
|
33bb1b2d29 | ||
|
|
3141191e43 | ||
|
|
536ea0b027 | ||
|
|
34e03bf4eb | ||
|
|
48c81b1d1e | ||
|
|
381d3031e6 | ||
|
|
a193e80f88 | ||
|
|
3410e20412 | ||
|
|
7f568d54bf | ||
|
|
619141ef94 | ||
|
|
e6128b3174 | ||
|
|
875e139bcd | ||
|
|
db97d126bf | ||
|
|
92597efce6 | ||
|
|
6b9cb35761 |
@@ -1,6 +1,6 @@
|
||||
# ffd-retief-hrd fanfictiondownloader
|
||||
application: fanfictiondownloader
|
||||
version: 4-4-46
|
||||
version: 4-4-52
|
||||
runtime: python27
|
||||
api_version: 1
|
||||
threadsafe: true
|
||||
|
||||
@@ -27,7 +27,7 @@ class FanFictionDownLoaderBase(InterfaceActionBase):
|
||||
description = 'UI plugin to download FanFiction stories from various sites.'
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
author = 'Jim Miller'
|
||||
version = (1, 7, 13)
|
||||
version = (1, 7, 19)
|
||||
minimum_calibre_version = (0, 8, 57)
|
||||
|
||||
#: This field defines the GUI plugin class that contains all the code
|
||||
|
||||
+46
-46
@@ -18,10 +18,10 @@ from PyQt4.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
from calibre.utils.config import JSONConfig
|
||||
from calibre.gui2.ui import get_gui
|
||||
|
||||
from calibre_plugins.fanfictiondownloader_plugin.prefs import prefs
|
||||
from calibre_plugins.fanfictiondownloader_plugin.prefs import prefs, PREFS_NAMESPACE
|
||||
from calibre_plugins.fanfictiondownloader_plugin.dialogs \
|
||||
import (UPDATE, UPDATEALWAYS, OVERWRITE, collision_order, RejectListDialog,
|
||||
EditTextDialog)
|
||||
EditTextDialog, RejectUrlEntry)
|
||||
|
||||
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters \
|
||||
import (getConfigSections, getNormalStoryURL)
|
||||
@@ -37,22 +37,16 @@ class RejectURLList:
|
||||
self.sync_lock = threading.RLock()
|
||||
self.listcache = None
|
||||
|
||||
def _read_list_from_text(self,text,addreasontext=None):
|
||||
cache = {}
|
||||
def _read_list_from_text(self,text,addreasontext=''):
|
||||
cache = OrderedDict()
|
||||
|
||||
#print("_read_list_from_text")
|
||||
for line in text.splitlines():
|
||||
if ',' in line:
|
||||
(rejurl,note) = line.split(',',1)
|
||||
else:
|
||||
(rejurl,note) = (line,'')
|
||||
rejurl = getNormalStoryURL(rejurl)
|
||||
if rejurl:
|
||||
if addreasontext and note:
|
||||
note = note +" - "+addreasontext
|
||||
elif addreasontext:
|
||||
note = addreasontext
|
||||
cache[rejurl] = note
|
||||
return cache
|
||||
|
||||
rue = RejectUrlEntry(line,addreasontext=addreasontext,fromline=True)
|
||||
#print("rue.url:%s"%rue.url)
|
||||
if rue.valid:
|
||||
cache[rue.url] = rue
|
||||
return cache
|
||||
|
||||
def _get_listcache(self):
|
||||
if self.listcache == None:
|
||||
@@ -60,26 +54,35 @@ class RejectURLList:
|
||||
return self.listcache
|
||||
|
||||
def _save_list(self,listcache):
|
||||
rejectlist = []
|
||||
for url in listcache:
|
||||
rejectlist.append("%s,%s"%(url,listcache[url]))
|
||||
|
||||
self.prefs['rejecturls'] = '\n'.join(rejectlist)
|
||||
#print("_save_list")
|
||||
self.prefs['rejecturls'] = '\n'.join([x.to_line() for x in listcache.values()])
|
||||
self.prefs.save_to_db()
|
||||
self.listcache = None
|
||||
|
||||
def clear_cache(self):
|
||||
self.listcache = None
|
||||
|
||||
# true if url is in list.
|
||||
def check(self,url):
|
||||
with self.sync_lock:
|
||||
listcache = self._get_listcache()
|
||||
return url in listcache
|
||||
|
||||
def get_note(self,url):
|
||||
with self.sync_lock:
|
||||
listcache = self._get_listcache()
|
||||
if url in listcache:
|
||||
note = listcache[url]
|
||||
return note
|
||||
|
||||
return listcache[url].note
|
||||
# not found
|
||||
return None
|
||||
return ''
|
||||
|
||||
def get_full_note(self,url):
|
||||
with self.sync_lock:
|
||||
listcache = self._get_listcache()
|
||||
if url in listcache:
|
||||
return listcache[url].fullnote()
|
||||
# not found
|
||||
return ''
|
||||
|
||||
def remove(self,url):
|
||||
with self.sync_lock:
|
||||
@@ -89,27 +92,26 @@ class RejectURLList:
|
||||
self._save_list(listcache)
|
||||
|
||||
def add_text(self,rejecttext,addreasontext):
|
||||
self.add(self._read_list_from_text(rejecttext,addreasontext).items())
|
||||
self.add(self._read_list_from_text(rejecttext,addreasontext).values())
|
||||
|
||||
def add(self,rejectlist,clear=False):
|
||||
# rejectlist=list of (url,note) tuples.
|
||||
with self.sync_lock:
|
||||
if clear:
|
||||
listcache={}
|
||||
listcache=OrderedDict()
|
||||
else:
|
||||
listcache = self._get_listcache()
|
||||
for (url,note) in rejectlist:
|
||||
listcache[url]=note
|
||||
for l in rejectlist:
|
||||
listcache[l.url]=l
|
||||
self._save_list(listcache)
|
||||
|
||||
def get_list(self):
|
||||
return copy.deepcopy(self._get_listcache())
|
||||
return self._get_listcache().values()
|
||||
|
||||
def get_reject_reasons(self):
|
||||
return self.prefs['rejectreasons'].splitlines()
|
||||
|
||||
rejecturllist = RejectURLList(prefs)
|
||||
|
||||
|
||||
class ConfigWidget(QWidget):
|
||||
|
||||
def __init__(self, plugin_action):
|
||||
@@ -173,6 +175,7 @@ class ConfigWidget(QWidget):
|
||||
prefs['adddialogstaysontop'] = self.basic_tab.adddialogstaysontop.isChecked()
|
||||
prefs['includeimages'] = self.basic_tab.includeimages.isChecked()
|
||||
prefs['lookforurlinhtml'] = self.basic_tab.lookforurlinhtml.isChecked()
|
||||
prefs['checkforseriesurlid'] = self.basic_tab.checkforseriesurlid.isChecked()
|
||||
prefs['injectseries'] = self.basic_tab.injectseries.isChecked()
|
||||
|
||||
if self.readinglist_tab:
|
||||
@@ -369,6 +372,11 @@ class BasicTab(QWidget):
|
||||
self.lookforurlinhtml.setChecked(prefs['lookforurlinhtml'])
|
||||
self.l.addWidget(self.lookforurlinhtml)
|
||||
|
||||
self.checkforseriesurlid = QCheckBox("Check for existing Series Anthology books?",self)
|
||||
self.checkforseriesurlid.setToolTip("Check for existings Series Anthology books using each new story's series URL before downloading.\nOffer to skip downloading if a Series Anthology is found.")
|
||||
self.checkforseriesurlid.setChecked(prefs['checkforseriesurlid'])
|
||||
self.l.addWidget(self.checkforseriesurlid)
|
||||
|
||||
self.injectseries = QCheckBox("Inject calibre Series when none found?",self)
|
||||
self.injectseries.setToolTip("If no series is found, inject the calibre series (if there is one) so it appears on the FFDL title page(not cover).")
|
||||
self.injectseries.setChecked(prefs['injectseries'])
|
||||
@@ -412,12 +420,8 @@ class BasicTab(QWidget):
|
||||
ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_()
|
||||
|
||||
def show_rejectlist(self):
|
||||
rejectlist = []
|
||||
for (url,note) in rejecturllist.get_list().items():
|
||||
rejectlist.append((None,url,note,note))
|
||||
|
||||
d = RejectListDialog(self,
|
||||
rejectlist,
|
||||
rejecturllist.get_list(),
|
||||
rejectreasons=rejecturllist.get_reject_reasons(),
|
||||
header="Edit Reject URLs List",
|
||||
show_delete=False,
|
||||
@@ -427,11 +431,7 @@ class BasicTab(QWidget):
|
||||
if d.result() != d.Accepted:
|
||||
return
|
||||
|
||||
rejectlist=[]
|
||||
for (bookid,url,note) in d.get_reject_list():
|
||||
rejectlist.append((url,note))
|
||||
|
||||
rejecturllist.add(rejectlist,clear=True)
|
||||
rejecturllist.add(d.get_reject_list(),clear=True)
|
||||
|
||||
def show_reject_reasons(self):
|
||||
d = EditTextDialog(self,
|
||||
@@ -446,11 +446,11 @@ class BasicTab(QWidget):
|
||||
|
||||
def add_reject_urls(self):
|
||||
d = EditTextDialog(self,
|
||||
"http://example.com?story.php?sid=5,Reason why I rejected it",
|
||||
"http://example.com/story.php?sid=5,Reason why I rejected it\nhttp://example.com/story.php?sid=6,Title by Author - Reason why I rejected it",
|
||||
icon=self.windowIcon(),
|
||||
title="Add Reject URLs",
|
||||
label="Add Reject URLs. Use: <b>http://...,note</b><br>Invalid story URLs will be ignored.",
|
||||
tooltip="One URL per line, everything after <b>,</b> will be put in the note.",
|
||||
label="Add Reject URLs. Use: <b>http://...,note</b> or <b>http://...,title by author - note</b><br>Invalid story URLs will be ignored.",
|
||||
tooltip="One URL per line:\n<b>http://...,note</b>\n<b>http://...,title by author - note</b>",
|
||||
rejectreasons=rejecturllist.get_reject_reasons(),
|
||||
reasonslabel='Add this reason to all URLs added:')
|
||||
d.exec_()
|
||||
|
||||
+254
-294
@@ -7,33 +7,27 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Jim Miller'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import traceback
|
||||
import traceback, re
|
||||
from functools import partial
|
||||
|
||||
import urllib
|
||||
import email
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.Qt import (QDialog, QTableWidget, QMessageBox, QVBoxLayout, QHBoxLayout,
|
||||
QGridLayout, QPushButton, QProgressDialog, QString, QLabel,
|
||||
QCheckBox, QIcon, QTextCursor, QTextEdit, QLineEdit, QInputDialog,
|
||||
QComboBox, QClipboard, QVariant, QProgressDialog, QTimer,
|
||||
QDialogButtonBox, QPixmap, Qt, QAbstractItemView, SIGNAL,
|
||||
QTableWidgetItem )
|
||||
from PyQt4.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QPushButton, QString, QLabel, QCheckBox, QIcon, QLineEdit,
|
||||
QComboBox, QVariant, QProgressDialog, QTimer, QDialogButtonBox,
|
||||
QPixmap, Qt, QAbstractItemView, SIGNAL, QTextEdit, pyqtSignal)
|
||||
|
||||
from calibre.gui2 import error_dialog, warning_dialog, question_dialog, info_dialog
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.gui2.complete2 import EditWithComplete
|
||||
|
||||
from calibre import confirm_config_name
|
||||
from calibre.gui2 import dynamic
|
||||
|
||||
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader import adapters,writers,exceptions
|
||||
from calibre_plugins.fanfictiondownloader_plugin.common_utils \
|
||||
import (ReadOnlyTableWidgetItem, ReadOnlyTextIconWidgetItem, SizePersistedDialog,
|
||||
ImageTitleLayout, get_icon)
|
||||
|
||||
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.geturls import get_urls_from_html, get_urls_from_text
|
||||
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters import getNormalStoryURL
|
||||
|
||||
SKIP=u'Skip'
|
||||
ADDNEW=u'Add New Book'
|
||||
@@ -53,7 +47,62 @@ collision_order=[SKIP,
|
||||
anthology_collision_order=[UPDATE,
|
||||
UPDATEALWAYS,
|
||||
OVERWRITEALWAYS]
|
||||
|
||||
class RejectUrlEntry:
|
||||
|
||||
matchpat=re.compile(r"^(?P<url>[^,]+)(,(?P<fullnote>(((?P<title>.+) by (?P<auth>.+?)( - (?P<note>.+))?)|.*)))?$")
|
||||
|
||||
def __init__(self,url_or_line,note=None,title=None,auth=None,
|
||||
addreasontext=None,fromline=False):
|
||||
|
||||
self.url=url_or_line
|
||||
self.note=note
|
||||
self.title=title
|
||||
self.auth=auth
|
||||
self.valid=False
|
||||
|
||||
if fromline:
|
||||
mc = re.match(self.matchpat,url_or_line)
|
||||
if mc:
|
||||
#print("mc:%s"%mc.groupdict())
|
||||
(url,title,auth,note) = mc.group('url','title','auth','note')
|
||||
if not mc.group('title'):
|
||||
title=''
|
||||
auth=''
|
||||
note=mc.group('fullnote')
|
||||
self.url=url
|
||||
self.note=note
|
||||
self.title=title
|
||||
self.auth=auth
|
||||
|
||||
if not self.note:
|
||||
if addreasontext:
|
||||
self.note = addreasontext
|
||||
else:
|
||||
self.note = ''
|
||||
else:
|
||||
if addreasontext:
|
||||
self.note = self.note + ' - ' + addreasontext
|
||||
|
||||
self.url = getNormalStoryURL(self.url)
|
||||
self.valid = self.url != None
|
||||
|
||||
def to_line(self):
|
||||
# always 'url,'
|
||||
return self.url+","+self.fullnote()
|
||||
|
||||
def fullnote(self):
|
||||
retval = ""
|
||||
if self.title and self.auth:
|
||||
retval = retval + "%s by %s"%(self.title,self.auth)
|
||||
if self.note:
|
||||
retval = retval + " - "
|
||||
|
||||
if self.note:
|
||||
retval = retval + self.note
|
||||
|
||||
return retval
|
||||
|
||||
# This is a more than slightly kludgey way to get
|
||||
# EditWithComplete to *not* alpha-order the reasons, but leave
|
||||
# them in the order entered. If
|
||||
@@ -81,18 +130,8 @@ class DroppableQTextEdit(QTextEdit):
|
||||
|
||||
def dropEvent(self,event):
|
||||
# print("event:%s"%event)
|
||||
# print("event.mimeData():%s"%event.mimeData())
|
||||
# print("event.mimeData().text():%s"%str(event.mimeData().text()))
|
||||
# print("event.mimeData().data():%s"%str(event.mimeData().data()))
|
||||
# print("event.mimeData().formats():%s"%[str(f) for f in event.mimeData().formats()])
|
||||
# for f in event.mimeData().formats():
|
||||
# try:
|
||||
# print("event.mimeData().data('%s'):%s"%(f,event.mimeData().data(f)))
|
||||
# except:
|
||||
# print("failed %s"%f)
|
||||
|
||||
mimetype='text/uri-list'
|
||||
# print("event.mimeData().data('%s'):%s"%(mimetype,event.mimeData().data(mimetype)))
|
||||
|
||||
urllist=[]
|
||||
filelist="%s"%event.mimeData().data(mimetype)
|
||||
@@ -133,25 +172,11 @@ class DroppableQTextEdit(QTextEdit):
|
||||
|
||||
class AddNewDialog(SizePersistedDialog):
|
||||
|
||||
def __init__(self, gui, prefs, icon, url_list_text, merge=False, newmerge=False):
|
||||
SizePersistedDialog.__init__(self, gui, 'FanFictionDownLoader plugin:add new dialog')
|
||||
self.gui = gui
|
||||
self.merge = merge
|
||||
self.newmerge = newmerge
|
||||
go_signal = pyqtSignal(object, object, object, object)
|
||||
|
||||
if merge:
|
||||
labeltext = 'Story URL(s) for anthology, one per line:'
|
||||
tooltiptext = 'URLs for stories to include in the anthology, one per line.\nWill take URLs from clipboard, but only valid URLs.'
|
||||
collisiontext = 'If Story Already Exists in Anthology?'
|
||||
collisiontooltip = "What to do if there's already an existing story with the same URL in the anthology."
|
||||
else:
|
||||
labeltext = 'Story URL(s), one per line:'
|
||||
tooltiptext = 'URLs for stories, one per line.\nWill take URLs from clipboard, but only valid URLs.\nAdd [1,5] after the URL to limit the download to chapters 1-5.'
|
||||
collisiontext = 'If Story Already Exists?'
|
||||
collisiontooltip = "What to do if there's already an existing story with the same URL or title and author."
|
||||
|
||||
if prefs['adddialogstaysontop']:
|
||||
QDialog.setWindowFlags ( self, Qt.Dialog|Qt.WindowStaysOnTopHint )
|
||||
def __init__(self, gui, prefs, icon):
|
||||
SizePersistedDialog.__init__(self, gui, 'FanFictionDownLoader plugin:add new dialog')
|
||||
self.prefs = prefs
|
||||
|
||||
self.setMinimumWidth(300)
|
||||
self.l = QVBoxLayout()
|
||||
@@ -160,69 +185,158 @@ class AddNewDialog(SizePersistedDialog):
|
||||
self.setWindowTitle('FanFictionDownLoader')
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.l.addWidget(QLabel(labeltext))
|
||||
self.toplabel=QLabel("Toplabel")
|
||||
self.l.addWidget(self.toplabel)
|
||||
self.url = DroppableQTextEdit(self)
|
||||
self.url.setToolTip(tooltiptext)
|
||||
self.url.setToolTip("UrlTooltip")
|
||||
self.url.setLineWrapMode(QTextEdit.NoWrap)
|
||||
self.url.setText(url_list_text)
|
||||
self.l.addWidget(self.url)
|
||||
|
||||
if not merge:
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel('Output &Format:')
|
||||
horz.addWidget(label)
|
||||
self.fileform = QComboBox(self)
|
||||
self.fileform.addItem('epub')
|
||||
self.fileform.addItem('mobi')
|
||||
self.fileform.addItem('html')
|
||||
self.fileform.addItem('txt')
|
||||
self.fileform.setCurrentIndex(self.fileform.findText(prefs['fileform']))
|
||||
self.fileform.setToolTip('Choose output format to create. May set default from plugin configuration.')
|
||||
self.fileform.activated.connect(self.set_collisions)
|
||||
|
||||
label.setBuddy(self.fileform)
|
||||
horz.addWidget(self.fileform)
|
||||
self.l.addLayout(horz)
|
||||
|
||||
if not newmerge:
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel(collisiontext)
|
||||
horz.addWidget(label)
|
||||
self.collision = QComboBox(self)
|
||||
self.collision.setToolTip(collisiontooltip)
|
||||
# add collision options
|
||||
self.set_collisions()
|
||||
i = self.collision.findText(prefs['collision'])
|
||||
if i > -1:
|
||||
self.collision.setCurrentIndex(i)
|
||||
label.setBuddy(self.collision)
|
||||
horz.addWidget(self.collision)
|
||||
self.l.addLayout(horz)
|
||||
|
||||
horz = QHBoxLayout()
|
||||
self.updatemeta = QCheckBox('Update Calibre &Metadata?',self)
|
||||
self.updatemeta.setToolTip("Update metadata for existing stories in Calibre from web site?\n(Columns set to 'New Only' in the column tabs will only be set for new books.)")
|
||||
self.updatemeta.setChecked(prefs['updatemeta'])
|
||||
horz.addWidget(self.updatemeta)
|
||||
|
||||
if not merge: # hide if anthology merge.
|
||||
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.setChecked(prefs['updateepubcover'])
|
||||
horz.addWidget(self.updateepubcover)
|
||||
|
||||
self.l.addLayout(horz)
|
||||
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
self.l.addWidget(button_box)
|
||||
self.merge = self.newmerge = False
|
||||
|
||||
if url_list_text:
|
||||
button_box.button(QDialogButtonBox.Ok).setFocus()
|
||||
# elements to hide when doing merge.
|
||||
self.mergehide = []
|
||||
# elements to show again when doing *update* merge
|
||||
self.mergeupdateshow = []
|
||||
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel('Output &Format:')
|
||||
self.mergehide.append(label)
|
||||
|
||||
self.fileform = QComboBox(self)
|
||||
self.fileform.addItem('epub')
|
||||
self.fileform.addItem('mobi')
|
||||
self.fileform.addItem('html')
|
||||
self.fileform.addItem('txt')
|
||||
self.fileform.setToolTip('Choose output format to create. May set default from plugin configuration.')
|
||||
self.fileform.activated.connect(self.set_collisions)
|
||||
|
||||
horz.addWidget(label)
|
||||
label.setBuddy(self.fileform)
|
||||
horz.addWidget(self.fileform)
|
||||
self.l.addLayout(horz)
|
||||
self.mergehide.append(self.fileform)
|
||||
|
||||
horz = QHBoxLayout()
|
||||
self.collisionlabel = QLabel("CollisionLabel")
|
||||
horz.addWidget(self.collisionlabel)
|
||||
self.collision = QComboBox(self)
|
||||
self.collision.setToolTip("CollisionToolTip")
|
||||
# add collision options
|
||||
self.set_collisions()
|
||||
i = self.collision.findText(prefs['collision'])
|
||||
if i > -1:
|
||||
self.collision.setCurrentIndex(i)
|
||||
self.collisionlabel.setBuddy(self.collision)
|
||||
horz.addWidget(self.collision)
|
||||
self.l.addLayout(horz)
|
||||
self.mergehide.append(self.collisionlabel)
|
||||
self.mergehide.append(self.collision)
|
||||
self.mergeupdateshow.append(self.collisionlabel)
|
||||
self.mergeupdateshow.append(self.collision)
|
||||
|
||||
horz = QHBoxLayout()
|
||||
self.updatemeta = QCheckBox('Update Calibre &Metadata?',self)
|
||||
self.updatemeta.setToolTip("Update metadata for existing stories in Calibre from web site?\n(Columns set to 'New Only' in the column tabs will only be set for new books.)")
|
||||
self.updatemeta.setChecked(prefs['updatemeta'])
|
||||
horz.addWidget(self.updatemeta)
|
||||
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.setChecked(prefs['updateepubcover'])
|
||||
horz.addWidget(self.updateepubcover)
|
||||
self.mergehide.append(self.updateepubcover)
|
||||
|
||||
self.l.addLayout(horz)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.ok_clicked)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
self.l.addWidget(self.button_box)
|
||||
|
||||
# invoke the
|
||||
def ok_clicked(self):
|
||||
self.hide()
|
||||
print("ok_clicked called")
|
||||
self.go_signal.emit( self.get_ffdl_options(),
|
||||
self.get_urlstext(),
|
||||
self.merge,
|
||||
self.extrapayload )
|
||||
|
||||
def show_dialog(self,
|
||||
url_list_text,
|
||||
callback,
|
||||
show=True,
|
||||
merge=False,
|
||||
newmerge=True,
|
||||
extraoptions={},
|
||||
extrapayload=None):
|
||||
# rather than mutex in ffdl_plugin, just bail here if it's
|
||||
# already in use.
|
||||
if self.isVisible(): return
|
||||
|
||||
try:
|
||||
self.go_signal.disconnect()
|
||||
except:
|
||||
pass # if not already connected.
|
||||
self.go_signal.connect(callback)
|
||||
|
||||
self.merge = merge
|
||||
self.newmerge = newmerge
|
||||
self.extraoptions = extraoptions
|
||||
self.extrapayload = extrapayload
|
||||
|
||||
if self.merge:
|
||||
self.toplabel.setText('Story URL(s) for anthology, one per line:')
|
||||
self.url.setToolTip('URLs for stories to include in the anthology, one per line.\nWill take URLs from clipboard, but only valid URLs.')
|
||||
self.collisionlabel.setText('If Story Already Exists in Anthology?')
|
||||
self.collision.setToolTip("What to do if there's already an existing story with the same URL in the anthology.")
|
||||
for widget in self.mergehide:
|
||||
widget.setVisible(False)
|
||||
if not self.newmerge:
|
||||
for widget in self.mergeupdateshow:
|
||||
widget.setVisible(True)
|
||||
else:
|
||||
for widget in self.mergehide:
|
||||
widget.setVisible(True)
|
||||
self.toplabel.setText('Story URL(s), one per line:')
|
||||
self.url.setToolTip('URLs for stories, one per line.\nWill take URLs from clipboard, but only valid URLs.\nAdd [1,5] after the URL to limit the download to chapters 1-5.')
|
||||
self.collisionlabel.setText('If Story Already Exists?')
|
||||
self.collision.setToolTip("What to do if there's already an existing story with the same URL or title and author.")
|
||||
|
||||
# Need to re-able after hiding/showing
|
||||
self.setAcceptDrops(True)
|
||||
self.url.setFocus()
|
||||
|
||||
if self.prefs['adddialogstaysontop']:
|
||||
QDialog.setWindowFlags ( self, Qt.Dialog | Qt.WindowStaysOnTopHint )
|
||||
else:
|
||||
QDialog.setWindowFlags ( self, Qt.Dialog )
|
||||
|
||||
if not self.merge:
|
||||
self.fileform.setCurrentIndex(self.fileform.findText(self.prefs['fileform']))
|
||||
|
||||
# add collision options
|
||||
self.set_collisions()
|
||||
|
||||
i = self.collision.findText(self.prefs['collision'])
|
||||
if i > -1:
|
||||
self.collision.setCurrentIndex(i)
|
||||
self.updatemeta.setChecked(self.prefs['updatemeta'])
|
||||
|
||||
if not self.merge:
|
||||
self.updateepubcover.setChecked(self.prefs['updateepubcover'])
|
||||
|
||||
self.url.setText(url_list_text)
|
||||
if url_list_text:
|
||||
self.button_box.button(QDialogButtonBox.Ok).setFocus()
|
||||
# restore saved size.
|
||||
self.resize_dialog()
|
||||
|
||||
if show: # so anthology update can be modal still.
|
||||
self.show()
|
||||
#self.resize(self.sizeHint())
|
||||
|
||||
def set_collisions(self):
|
||||
@@ -240,27 +354,21 @@ class AddNewDialog(SizePersistedDialog):
|
||||
self.collision.setCurrentIndex(i)
|
||||
|
||||
def get_ffdl_options(self):
|
||||
retval = {
|
||||
'fileform': unicode(self.fileform.currentText()),
|
||||
'collision': unicode(self.collision.currentText()),
|
||||
'updatemeta': self.updatemeta.isChecked(),
|
||||
'updateepubcover': self.updateepubcover.isChecked(),
|
||||
}
|
||||
|
||||
if self.merge:
|
||||
retval['fileform']=='epub'
|
||||
retval['updateepubcover']=True
|
||||
if self.newmerge:
|
||||
updatemeta=True
|
||||
collision=ADDNEW
|
||||
else:
|
||||
updatemeta=self.updatemeta.isChecked()
|
||||
collision=unicode(self.collision.currentText())
|
||||
|
||||
return {
|
||||
'fileform': 'epub',
|
||||
'collision': collision,
|
||||
'updatemeta': updatemeta,
|
||||
'updateepubcover': True,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'fileform': unicode(self.fileform.currentText()),
|
||||
'collision': unicode(self.collision.currentText()),
|
||||
'updatemeta': self.updatemeta.isChecked(),
|
||||
'updateepubcover': self.updateepubcover.isChecked(),
|
||||
}
|
||||
retval['updatemeta']=True
|
||||
retval['collision']=ADDNEW
|
||||
|
||||
return dict(retval.items() + self.extraoptions.items() )
|
||||
|
||||
def get_urlstext(self):
|
||||
return unicode(self.url.toPlainText())
|
||||
@@ -279,7 +387,6 @@ class CollectURLDialog(SizePersistedDialog):
|
||||
'''
|
||||
def __init__(self, gui, title, url_text, epubmerge_plugin=None):
|
||||
SizePersistedDialog.__init__(self, gui, 'FanFictionDownLoader plugin:get story urls')
|
||||
self.gui = gui
|
||||
self.status=False
|
||||
self.anthology=False
|
||||
|
||||
@@ -333,7 +440,6 @@ class UserPassDialog(QDialog):
|
||||
'''
|
||||
def __init__(self, gui, site, exception=None):
|
||||
QDialog.__init__(self, gui)
|
||||
self.gui = gui
|
||||
self.status=False
|
||||
|
||||
self.l = QGridLayout()
|
||||
@@ -392,7 +498,6 @@ class LoopProgressDialog(QProgressDialog):
|
||||
QString(), 0, len(book_list), gui)
|
||||
self.setWindowTitle(win_title)
|
||||
self.setMinimumWidth(500)
|
||||
self.gui = gui
|
||||
self.book_list = book_list
|
||||
self.foreach_function = foreach_function
|
||||
self.finish_function = finish_function
|
||||
@@ -441,7 +546,6 @@ class LoopProgressDialog(QProgressDialog):
|
||||
|
||||
def do_when_finished(self):
|
||||
self.hide()
|
||||
self.gui = None
|
||||
# Queues a job to process these books in the background.
|
||||
self.finish_function(self.book_list)
|
||||
|
||||
@@ -490,7 +594,6 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
def __init__(self, gui, header, prefs, icon, books,
|
||||
save_size_name='fanfictiondownloader_plugin:update list dialog'):
|
||||
SizePersistedDialog.__init__(self, gui, save_size_name)
|
||||
self.gui = gui
|
||||
|
||||
self.setWindowTitle(header)
|
||||
self.setWindowIcon(icon)
|
||||
@@ -508,11 +611,7 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
|
||||
button_layout = QVBoxLayout()
|
||||
books_layout.addLayout(button_layout)
|
||||
# self.move_up_button = QtGui.QToolButton(self)
|
||||
# self.move_up_button.setToolTip('Move selected books up the list')
|
||||
# self.move_up_button.setIcon(QIcon(I('arrow-up.png')))
|
||||
# self.move_up_button.clicked.connect(self.books_table.move_rows_up)
|
||||
# button_layout.addWidget(self.move_up_button)
|
||||
|
||||
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem)
|
||||
self.remove_button = QtGui.QToolButton(self)
|
||||
@@ -522,11 +621,6 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
button_layout.addWidget(self.remove_button)
|
||||
spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem1)
|
||||
# self.move_down_button = QtGui.QToolButton(self)
|
||||
# self.move_down_button.setToolTip('Move selected books down the list')
|
||||
# self.move_down_button.setIcon(QIcon(I('arrow-down.png')))
|
||||
# self.move_down_button.clicked.connect(self.books_table.move_rows_down)
|
||||
# button_layout.addWidget(self.move_down_button)
|
||||
|
||||
options_layout = QHBoxLayout()
|
||||
|
||||
@@ -552,7 +646,6 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
i = self.collision.findText(prefs['collision'])
|
||||
if i > -1:
|
||||
self.collision.setCurrentIndex(i)
|
||||
# self.collision.setToolTip('Overwrite will replace the existing story. Add New will create a new story with the same title and author.')
|
||||
label.setBuddy(self.collision)
|
||||
options_layout.addWidget(self.collision)
|
||||
|
||||
@@ -661,11 +754,9 @@ class StoryListTableWidget(QTableWidget):
|
||||
self.setItem(row, 2, AuthorTableWidgetItem(", ".join(book['author']), ", ".join(book['author_sort'])))
|
||||
|
||||
url_cell = ReadOnlyTableWidgetItem(book['url'])
|
||||
#url_cell.setData(Qt.UserRole, QVariant(book['url']))
|
||||
self.setItem(row, 3, url_cell)
|
||||
|
||||
comment_cell = ReadOnlyTableWidgetItem(book['comment'])
|
||||
#comment_cell.setData(Qt.UserRole, QVariant(book))
|
||||
self.setItem(row, 4, comment_cell)
|
||||
|
||||
def get_books(self):
|
||||
@@ -699,56 +790,6 @@ class StoryListTableWidget(QTableWidget):
|
||||
self.selectRow(row)
|
||||
self.scrollToItem(self.currentItem())
|
||||
|
||||
def move_rows_up(self):
|
||||
self.setFocus()
|
||||
rows = self.selectionModel().selectedRows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
first_sel_row = rows[0].row()
|
||||
if first_sel_row <= 0:
|
||||
return
|
||||
# Workaround for strange selection bug in Qt which "alters" the selection
|
||||
# in certain circumstances which meant move down only worked properly "once"
|
||||
selrows = []
|
||||
for row in rows:
|
||||
selrows.append(row.row())
|
||||
selrows.sort()
|
||||
for selrow in selrows:
|
||||
self.swap_row_widgets(selrow - 1, selrow + 1)
|
||||
scroll_to_row = first_sel_row - 1
|
||||
if scroll_to_row > 0:
|
||||
scroll_to_row = scroll_to_row - 1
|
||||
self.scrollToItem(self.item(scroll_to_row, 0))
|
||||
|
||||
def move_rows_down(self):
|
||||
self.setFocus()
|
||||
rows = self.selectionModel().selectedRows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
last_sel_row = rows[-1].row()
|
||||
if last_sel_row == self.rowCount() - 1:
|
||||
return
|
||||
# Workaround for strange selection bug in Qt which "alters" the selection
|
||||
# in certain circumstances which meant move down only worked properly "once"
|
||||
selrows = []
|
||||
for row in rows:
|
||||
selrows.append(row.row())
|
||||
selrows.sort()
|
||||
for selrow in reversed(selrows):
|
||||
self.swap_row_widgets(selrow + 2, selrow)
|
||||
scroll_to_row = last_sel_row + 1
|
||||
if scroll_to_row < self.rowCount() - 1:
|
||||
scroll_to_row = scroll_to_row + 1
|
||||
self.scrollToItem(self.item(scroll_to_row, 0))
|
||||
|
||||
def swap_row_widgets(self, src_row, dest_row):
|
||||
self.blockSignals(True)
|
||||
self.insertRow(dest_row)
|
||||
for col in range(0, self.columnCount()):
|
||||
self.setItem(dest_row, col, self.takeItem(src_row, col))
|
||||
self.removeRow(src_row)
|
||||
self.blockSignals(False)
|
||||
|
||||
class RejectListTableWidget(QTableWidget):
|
||||
|
||||
def __init__(self, parent,rejectreasons=[]):
|
||||
@@ -756,85 +797,53 @@ class RejectListTableWidget(QTableWidget):
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.rejectreasons = rejectreasons
|
||||
|
||||
def on_headersection_clicked(self):
|
||||
self.setSortingEnabled(True)
|
||||
|
||||
def populate_table(self, reject_list):
|
||||
self.clear()
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setRowCount(len(reject_list))
|
||||
header_labels = ['URL', 'Note']
|
||||
header_labels = ['URL', 'Title', 'Author', 'Note']
|
||||
self.setColumnCount(len(header_labels))
|
||||
self.setHorizontalHeaderLabels(header_labels)
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
#self.verticalHeader().setDefaultSectionSize(24)
|
||||
self.verticalHeader().hide()
|
||||
|
||||
# need sortingEnbled to sort, but off to up & down.
|
||||
self.connect(self.horizontalHeader(),
|
||||
SIGNAL('sectionClicked(int)'),
|
||||
self.on_headersection_clicked)
|
||||
|
||||
# it's generally recommended to enable sort after pop, not
|
||||
# before. But then it needs to be sorted on a column and I'd
|
||||
# rather keep the order given.
|
||||
self.setSortingEnabled(True)
|
||||
# row is just row number.
|
||||
for row, rejectrow in enumerate(reject_list):
|
||||
#print("populating table:%s"%rejectrow.to_line())
|
||||
self.populate_table_row(row,rejectrow)
|
||||
|
||||
self.resizeColumnsToContents()
|
||||
self.setMinimumColumnWidth(1, 100)
|
||||
self.setMinimumColumnWidth(2, 100)
|
||||
self.setMinimumColumnWidth(0, 100)
|
||||
self.setMinimumColumnWidth(3, 100)
|
||||
self.setMinimumSize(300, 0)
|
||||
|
||||
def setMinimumColumnWidth(self, col, minimum):
|
||||
if self.columnWidth(col) < minimum:
|
||||
self.setColumnWidth(col, minimum)
|
||||
|
||||
def populate_table_row(self, row, rejectrow):
|
||||
|
||||
(bookid,url,titleauth,oldrejnote) = rejectrow
|
||||
if oldrejnote:
|
||||
noteprefix = note = oldrejnote
|
||||
# incase the existing note ends with one of the known reasons.
|
||||
for reason in self.rejectreasons:
|
||||
if noteprefix.endswith(' - '+reason):
|
||||
noteprefix = noteprefix[:-len(' - '+reason)]
|
||||
break
|
||||
else:
|
||||
noteprefix = note = titleauth
|
||||
|
||||
if len(noteprefix) > 0:
|
||||
noteprefix = noteprefix+' - '
|
||||
|
||||
url_cell = ReadOnlyTableWidgetItem(url)
|
||||
url_cell.setData(Qt.UserRole, QVariant(bookid))
|
||||
url_cell.setToolTip('URL to add to the Reject List.')
|
||||
self.setItem(row, 0, url_cell)
|
||||
def populate_table_row(self, row, rej):
|
||||
|
||||
self.setItem(row, 0, ReadOnlyTableWidgetItem(rej.url))
|
||||
self.setItem(row, 1, ReadOnlyTableWidgetItem(rej.title))
|
||||
self.setItem(row, 2, ReadOnlyTableWidgetItem(rej.auth))
|
||||
|
||||
note_cell = EditWithComplete(self)
|
||||
|
||||
note_cell.lineEdit().mcompleter.model().set_items = \
|
||||
partial(complete_model_set_items_kludge,
|
||||
note_cell.lineEdit().mcompleter.model())
|
||||
|
||||
items = [note]+[ noteprefix+x for x in self.rejectreasons ]
|
||||
items = [rej.note]+self.rejectreasons
|
||||
note_cell.update_items_cache(items)
|
||||
note_cell.show_initial_value(note)
|
||||
note_cell.show_initial_value(rej.note)
|
||||
note_cell.set_separator(None)
|
||||
note_cell.setToolTip('Select or Edit Reject Note.')
|
||||
self.setCellWidget(row, 1, note_cell)
|
||||
self.setCellWidget(row, 3, note_cell)
|
||||
|
||||
# note_cell = QTableWidgetItem(note)
|
||||
# note_cell.setToolTip('Double-click to edit note.')
|
||||
# self.setItem(row, 1, note_cell)
|
||||
|
||||
def get_reject_list(self):
|
||||
rejectrows = []
|
||||
for row in range(self.rowCount()):
|
||||
bookid = self.item(row, 0).data(Qt.UserRole).toPyObject()
|
||||
url = unicode(self.item(row, 0).text())
|
||||
note = unicode(self.cellWidget(row, 1).currentText()).strip()
|
||||
rejectrows.append((bookid,url,note))
|
||||
return rejectrows
|
||||
|
||||
def remove_selected_rows(self):
|
||||
self.setFocus()
|
||||
rows = self.selectionModel().selectedRows()
|
||||
@@ -857,57 +866,6 @@ class RejectListTableWidget(QTableWidget):
|
||||
self.selectRow(row)
|
||||
self.scrollToItem(self.currentItem())
|
||||
|
||||
def move_rows_up(self):
|
||||
self.setFocus()
|
||||
rows = self.selectionModel().selectedRows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
first_sel_row = rows[0].row()
|
||||
if first_sel_row <= 0:
|
||||
return
|
||||
# Workaround for strange selection bug in Qt which "alters" the selection
|
||||
# in certain circumstances which meant move down only worked properly "once"
|
||||
selrows = []
|
||||
for row in rows:
|
||||
selrows.append(row.row())
|
||||
selrows.sort()
|
||||
for selrow in selrows:
|
||||
self.swap_row_widgets(selrow - 1, selrow + 1)
|
||||
scroll_to_row = first_sel_row - 1
|
||||
if scroll_to_row > 0:
|
||||
scroll_to_row = scroll_to_row - 1
|
||||
self.scrollToItem(self.item(scroll_to_row, 0))
|
||||
|
||||
def move_rows_down(self):
|
||||
self.setFocus()
|
||||
rows = self.selectionModel().selectedRows()
|
||||
if len(rows) == 0:
|
||||
return
|
||||
last_sel_row = rows[-1].row()
|
||||
if last_sel_row == self.rowCount() - 1:
|
||||
return
|
||||
# Workaround for strange selection bug in Qt which "alters" the selection
|
||||
# in certain circumstances which meant move down only worked properly "once"
|
||||
selrows = []
|
||||
for row in rows:
|
||||
selrows.append(row.row())
|
||||
selrows.sort()
|
||||
for selrow in reversed(selrows):
|
||||
self.swap_row_widgets(selrow + 2, selrow)
|
||||
scroll_to_row = last_sel_row + 1
|
||||
if scroll_to_row < self.rowCount() - 1:
|
||||
scroll_to_row = scroll_to_row + 1
|
||||
self.scrollToItem(self.item(scroll_to_row, 0))
|
||||
|
||||
def swap_row_widgets(self, src_row, dest_row):
|
||||
self.blockSignals(True)
|
||||
self.setSortingEnabled(False)
|
||||
self.insertRow(dest_row)
|
||||
for col in range(0, self.columnCount()):
|
||||
self.setItem(dest_row, col, self.takeItem(src_row, col))
|
||||
self.removeRow(src_row)
|
||||
self.blockSignals(False)
|
||||
|
||||
class RejectListDialog(SizePersistedDialog):
|
||||
def __init__(self, gui, reject_list,
|
||||
rejectreasons=[],
|
||||
@@ -917,7 +875,6 @@ class RejectListDialog(SizePersistedDialog):
|
||||
show_all_reasons=True,
|
||||
save_size_name='ffdl:reject list dialog'):
|
||||
SizePersistedDialog.__init__(self, gui, save_size_name)
|
||||
self.gui = gui
|
||||
|
||||
self.setWindowTitle(header)
|
||||
self.setWindowIcon(get_icon(icon))
|
||||
@@ -937,21 +894,13 @@ class RejectListDialog(SizePersistedDialog):
|
||||
rejects_layout.addLayout(button_layout)
|
||||
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem)
|
||||
# self.move_up_button = QtGui.QToolButton(self)
|
||||
# self.move_up_button.setToolTip('Move selected books up the list')
|
||||
# self.move_up_button.setIcon(QIcon(I('arrow-up.png')))
|
||||
# self.move_up_button.clicked.connect(self.books_table.move_rows_up)
|
||||
# button_layout.addWidget(self.move_up_button)
|
||||
|
||||
self.remove_button = QtGui.QToolButton(self)
|
||||
self.remove_button.setToolTip('Remove selected URL(s) from the list')
|
||||
self.remove_button.setIcon(get_icon('list_remove.png'))
|
||||
self.remove_button.clicked.connect(self.remove_from_list)
|
||||
button_layout.addWidget(self.remove_button)
|
||||
# self.move_down_button = QtGui.QToolButton(self)
|
||||
# self.move_down_button.setToolTip('Move selected books down the list')
|
||||
# self.move_down_button.setIcon(QIcon(I('arrow-down.png')))
|
||||
# self.move_down_button.clicked.connect(self.books_table.move_rows_down)
|
||||
# button_layout.addWidget(self.move_down_button)
|
||||
|
||||
spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem1)
|
||||
|
||||
@@ -998,10 +947,21 @@ class RejectListDialog(SizePersistedDialog):
|
||||
self.rejects_table.remove_selected_rows()
|
||||
|
||||
def get_reject_list(self):
|
||||
return self.rejects_table.get_reject_list()
|
||||
rejectrows = []
|
||||
for row in range(self.rejects_table.rowCount()):
|
||||
url = unicode(self.rejects_table.item(row, 0).text()).strip()
|
||||
title = unicode(self.rejects_table.item(row, 1).text()).strip()
|
||||
auth = unicode(self.rejects_table.item(row, 2).text()).strip()
|
||||
note = unicode(self.rejects_table.cellWidget(row, 3).currentText()).strip()
|
||||
rejectrows.append(RejectUrlEntry(url,note,title,auth,self.get_reason_text()))
|
||||
return rejectrows
|
||||
|
||||
def get_reason_text(self):
|
||||
return unicode(self.reason_edit.currentText()).strip()
|
||||
try:
|
||||
return unicode(self.reason_edit.currentText()).strip()
|
||||
except:
|
||||
# doesn't have self.reason_edit when editing existing list.
|
||||
return None
|
||||
|
||||
def get_deletebooks(self):
|
||||
return self.deletebooks.isChecked()
|
||||
|
||||
@@ -47,7 +47,7 @@ from calibre_plugins.fanfictiondownloader_plugin.dialogs import (
|
||||
AddNewDialog, UpdateExistingDialog,
|
||||
LoopProgressDialog, UserPassDialog, AboutDialog, CollectURLDialog, RejectListDialog,
|
||||
OVERWRITE, OVERWRITEALWAYS, UPDATE, UPDATEALWAYS, ADDNEW, SKIP, CALIBREONLY,
|
||||
NotGoingToDownload )
|
||||
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,
|
||||
@@ -102,7 +102,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# are not found in the zip file will result in null QIcons.
|
||||
icon = get_icon('images/icon.png')
|
||||
|
||||
#self.qaction.setText('FFDL')
|
||||
self.qaction.setText('FanFictionDL')
|
||||
|
||||
# The qaction is automatically created from the action_spec defined
|
||||
# above
|
||||
@@ -126,6 +126,10 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# been displayed once.
|
||||
self.rebuild_menus()
|
||||
|
||||
self.add_new_dialog = AddNewDialog(self.gui,
|
||||
prefs,
|
||||
self.qaction.icon())
|
||||
|
||||
## Kludgey, yes, but with the real configuration inside the
|
||||
## library now, how else would a user be able to change this
|
||||
## setting if it's crashing calibre?
|
||||
@@ -136,7 +140,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
file_path = os.path.join(calibre_config_dir,
|
||||
*("plugins/fanfictiondownloader_macmenuhack.txt".split('/')))
|
||||
file_path = os.path.abspath(file_path)
|
||||
print("macmenuhack file_path:%s"%file_path)
|
||||
print("Plugin %s macmenuhack file_path:%s"%(self.name,file_path))
|
||||
self.macmenuhack = os.access(file_path, os.F_OK)
|
||||
return self.macmenuhack
|
||||
|
||||
@@ -399,17 +403,17 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# want title/author, too, for rejects.
|
||||
self.populate_book_from_calibre_id(book,db)
|
||||
if book['url']:
|
||||
# get existing note, if there is one.
|
||||
book['oldrejnote']=rejecturllist.check(book['url'])
|
||||
# get existing note, if on rejected list.
|
||||
book['oldrejnote']=rejecturllist.get_note(book['url'])
|
||||
|
||||
def reject_list_urls_finish(self, book_list):
|
||||
|
||||
# construct reject list of tuples:
|
||||
# (calibre_id, url, "title, authors", old reject note).
|
||||
reject_list = [ ( x['calibre_id'],x['url'],
|
||||
"%s by %s"%(x['title'],
|
||||
', '.join(x['author'])),
|
||||
x['oldrejnote'])
|
||||
reject_list = [ RejectUrlEntry(x['url'],
|
||||
x['oldrejnote'],
|
||||
x['title'],
|
||||
', '.join(x['author']))
|
||||
for x in book_list if x['good'] ]
|
||||
if reject_list:
|
||||
d = RejectListDialog(self.gui,reject_list,
|
||||
@@ -419,19 +423,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
if d.result() != d.Accepted:
|
||||
return
|
||||
|
||||
bookids=[]
|
||||
rejectlist=[]
|
||||
addreasontext=d.get_reason_text()
|
||||
for (bookid,url,note) in d.get_reject_list():
|
||||
bookids.append(bookid)
|
||||
if addreasontext and note:
|
||||
note = note +" - "+addreasontext
|
||||
elif addreasontext:
|
||||
note = addreasontext
|
||||
rejectlist.append((url,note))
|
||||
print("Adding (%s) to Reject List: %s"%(url,note))
|
||||
|
||||
rejecturllist.add(rejectlist)
|
||||
rejecturllist.add(d.get_reject_list())
|
||||
|
||||
if d.get_deletebooks():
|
||||
self.gui.iactions['Remove Books'].delete_books()
|
||||
@@ -440,41 +432,22 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
message="<p>Rejecting FFDL URLs: None of the books selected have FanFiction URLs.</p><p>Proceed to Remove?</p>"
|
||||
if confirm(message,'fanfictiondownloader_reject_non_fanfiction', self.gui):
|
||||
self.gui.iactions['Remove Books'].delete_books()
|
||||
|
||||
def add_dialog(self,url_list_text=None,merge=False,anthology_url=None):
|
||||
|
||||
#print("add_dialog()")
|
||||
def add_dialog(self,url_list_text=None,merge=False,anthology_url=None):
|
||||
'Both new individual stories and new anthologies are created here.'
|
||||
|
||||
if not url_list_text:
|
||||
url_list = self.get_urls_clip()
|
||||
url_list_text = "\n".join(url_list)
|
||||
|
||||
# self.gui is the main calibre GUI. It acts as the gateway to access
|
||||
# all the elements of the calibre user interface, it should also be the
|
||||
# parent of the dialog
|
||||
# AddNewDialog just collects URLs, format and presents buttons.
|
||||
d = AddNewDialog(self.gui,
|
||||
prefs,
|
||||
self.qaction.icon(),
|
||||
url_list_text,
|
||||
merge=merge,
|
||||
newmerge=merge # if here, it's a new anthology.
|
||||
)
|
||||
d.exec_()
|
||||
if d.result() != d.Accepted:
|
||||
return
|
||||
|
||||
url_list = split_text_to_urls(d.get_urlstext())
|
||||
add_books = self.convert_urls_to_books(url_list)
|
||||
#print("add_books:%s"%add_books)
|
||||
#print("options:%s"%d.get_ffdl_options())
|
||||
|
||||
options = d.get_ffdl_options()
|
||||
options['version'] = self.version
|
||||
options['anthology_url']=anthology_url
|
||||
print(self.version)
|
||||
|
||||
self.prep_downloads( options, add_books, merge=merge )
|
||||
# AddNewDialog collects URLs, format and presents buttons.
|
||||
# add_new_dialog is modeless and reused, both for new stories
|
||||
# and anthologies, and for updating existing anthologies.
|
||||
self.add_new_dialog.show_dialog(url_list_text,
|
||||
self.prep_downloads,
|
||||
merge=merge,
|
||||
newmerge=True,
|
||||
extraoptions={'anthology_url':anthology_url})
|
||||
|
||||
def update_anthology(self):
|
||||
if not self.get_epubmerge_plugin():
|
||||
@@ -488,7 +461,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
if len(self.gui.library_view.get_selected_ids()) != 1:
|
||||
self.gui.status_bar.show_message(_('Can only update 1 anthology at a time'), 3000)
|
||||
return
|
||||
#print("update_existing()")
|
||||
|
||||
db = self.gui.current_db
|
||||
book_id = self.gui.library_view.get_selected_ids()[0]
|
||||
@@ -530,25 +502,30 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
url_list_text = "\n".join(url_list)
|
||||
|
||||
#print("urlmapfile:%s"%urlmapfile)
|
||||
|
||||
# AddNewDialog collects URLs, format and presents buttons.
|
||||
# add_new_dialog is modeless and reused, both for new stories
|
||||
# and anthologies, and for updating existing anthologies.
|
||||
self.add_new_dialog.show_dialog(url_list_text,
|
||||
self.prep_anthology_downloads,
|
||||
show=False,
|
||||
merge=True,
|
||||
newmerge=False,
|
||||
extrapayload=urlmapfile,
|
||||
extraoptions={'tdir':tdir,
|
||||
'mergebook':mergebook})
|
||||
# Need to use AddNewDialog modal here because it's an update
|
||||
# of an existing book. Don't want the user deleting it or
|
||||
# switching libraries on us.
|
||||
self.add_new_dialog.exec_()
|
||||
|
||||
# self.gui is the main calibre GUI. It acts as the gateway to access
|
||||
# all the elements of the calibre user interface, it should also be the
|
||||
# parent of the dialog
|
||||
# AddNewDialog just collects URLs, format and presents buttons.
|
||||
d = AddNewDialog(self.gui,
|
||||
prefs,
|
||||
self.qaction.icon(),
|
||||
url_list_text,
|
||||
merge=True,
|
||||
newmerge=False
|
||||
)
|
||||
d.exec_()
|
||||
if d.result() != d.Accepted:
|
||||
return
|
||||
|
||||
url_list = split_text_to_urls(d.get_urlstext())
|
||||
|
||||
update_books = self.convert_urls_to_books(url_list)
|
||||
def prep_anthology_downloads(self, options, update_books,
|
||||
merge=False, urlmapfile=None):
|
||||
|
||||
if isinstance(update_books,basestring):
|
||||
url_list = split_text_to_urls(update_books)
|
||||
update_books = self.convert_urls_to_books(url_list)
|
||||
|
||||
for j, book in enumerate(update_books):
|
||||
url = book['url']
|
||||
@@ -574,13 +551,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
print("Canceling anthology update due to removed stories.")
|
||||
return
|
||||
|
||||
options = d.get_ffdl_options()
|
||||
options['version'] = self.version
|
||||
options['tdir'] = tdir
|
||||
#options['collision'] = UPDATEALWAYS
|
||||
print(self.version)
|
||||
|
||||
options['mergebook'] = mergebook
|
||||
# Now that we've
|
||||
self.prep_downloads( options, update_books, merge=True )
|
||||
|
||||
def update_dialog(self):
|
||||
@@ -631,8 +602,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# only if there's some good ones.
|
||||
if 0 < len(filter(lambda x : x['good'], update_books)):
|
||||
options = d.get_ffdl_options()
|
||||
options['version'] = self.version
|
||||
print(self.version)
|
||||
self.prep_downloads( options, update_books )
|
||||
|
||||
def get_urls_clip(self,storyurls=True):
|
||||
@@ -648,9 +617,16 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# No need to do anything with perfs here, but we could.
|
||||
prefs
|
||||
|
||||
def prep_downloads(self, options, books, merge=False):
|
||||
def prep_downloads(self, options, books, merge=False, extrapayload=None):
|
||||
'''Fetch metadata for stories from servers, launch BG job when done.'''
|
||||
|
||||
if isinstance(books,basestring):
|
||||
url_list = split_text_to_urls(books)
|
||||
books = self.convert_urls_to_books(url_list)
|
||||
|
||||
options['version'] = self.version
|
||||
print(self.version)
|
||||
|
||||
#print("prep_downloads:%s"%books)
|
||||
|
||||
if 'tdir' not in options: # if merging an anthology, there's alread a tdir.
|
||||
@@ -687,12 +663,13 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
print("url:%s"%url)
|
||||
|
||||
if not merge: # skip reject list when merging.
|
||||
rejnote = rejecturllist.check(url)
|
||||
if rejnote:
|
||||
if rejecturllist.check(url):
|
||||
rejnote = rejecturllist.get_full_note(url)
|
||||
if question_dialog(self.gui, 'Reject URL?',
|
||||
'<p>Reject URL?</p>'+
|
||||
'<p>%s is on the Reject URL list:<br />"%s"</p>'%(url,rejnote)+
|
||||
"<p>Click 'No' to download anyway.</p>",
|
||||
'<h3>Reject URL?</h3>'+
|
||||
'<p><b>%s</b> is on your Reject URL list:</p><p>"<b>%s</b>"</p>'%(url,rejnote)+
|
||||
"<p>Click '<b>Yes</b>' to Reject.</p>"+
|
||||
"<p>Click '<b>No</b>' to download anyway.</p>",
|
||||
show_copy_button=False):
|
||||
book['comment'] = "Story on Reject URLs list (%s)."%rejnote
|
||||
book['good']=False
|
||||
@@ -701,9 +678,10 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
return
|
||||
else:
|
||||
if question_dialog(self.gui, 'Remove Reject URL?',
|
||||
"<p>Remove URL from Reject List?</p>"+
|
||||
'<p>%s is on the Reject URL list:<br />"%s"</p>'%(url,rejnote)+
|
||||
"<p>Click 'Yes' to remove it from the list and download,<br /> 'No' to download, but leave it on the Reject list.</p>",
|
||||
"<h3>Remove URL from Reject List?</h3>"+
|
||||
'<p><b>%s</b> is on your Reject URL list:</p><p>"<b>%s</b>"</p>'%(url,rejnote)+
|
||||
"<p>Click '<b>Yes</b>' to remove it from the list,</p>"+
|
||||
"<p>Click '<b>No</b>' to leave it on the list.</p>",
|
||||
show_copy_button=False):
|
||||
rejecturllist.remove(url)
|
||||
|
||||
@@ -753,6 +731,31 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# let other exceptions percolate up.
|
||||
story = adapter.getStoryMetadataOnly()
|
||||
|
||||
series = story.getMetadata('series')
|
||||
if not merge and series and prefs['checkforseriesurlid']:
|
||||
# try to find *series anthology* by *seriesUrl* identifier url or uri first.
|
||||
searchstr = 'identifiers:"~ur(i|l):=%s"'%story.getMetadata('seriesUrl').replace(":","|")
|
||||
identicalbooks = db.search_getting_ids(searchstr, None)
|
||||
# print("searchstr:%s"%searchstr)
|
||||
# print("identicalbooks:%s"%identicalbooks)
|
||||
if len(identicalbooks) > 0 and question_dialog(self.gui, 'Skip Story?',
|
||||
'<h3>Skip Anthology Story?</h3>'+
|
||||
'<p>"<b>%s</b>" is in series "<b><a href="%s">%s</a></b>" that you have an anthology book for.</p>'%
|
||||
(story.getMetadata('title'),story.getMetadata('seriesUrl'),series[:series.index(' [')])+
|
||||
"<p>Click '<b>Yes</b>' to Skip.</p>"+
|
||||
"<p>Click '<b>No</b>' to download anyway.</p>",
|
||||
show_copy_button=False):
|
||||
book['comment'] = "Story in Series Anthology(%s)."%series
|
||||
book['title'] = story.getMetadata('title')
|
||||
book['author'] = [story.getMetadata('author')]
|
||||
book['good']=False
|
||||
book['icon']='rotate-right.png'
|
||||
book['status'] = 'Skipped'
|
||||
return
|
||||
|
||||
|
||||
################################################################################################################################################33
|
||||
|
||||
# set PI version instead of default.
|
||||
if 'version' in options:
|
||||
story.setMetadata('version',options['version'])
|
||||
@@ -1363,9 +1366,13 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
else:
|
||||
coldef = custom_columns[custcol]
|
||||
label = coldef['label']
|
||||
|
||||
|
||||
if flag == 'r' or book['added']: # flag 'n' isn't actually needed--*always* set if configured and new book.
|
||||
db.set_custom(book_id, book['all_metadata'][meta], label=label, commit=False)
|
||||
if coldef['datatype'] in ('int','float'): # for favs, etc--site specific metadata.
|
||||
val = unicode(book['all_metadata'][meta]).replace(",","")
|
||||
else:
|
||||
val = book['all_metadata'][meta]
|
||||
db.set_custom(book_id, val, label=label, commit=False)
|
||||
|
||||
if flag == 'a':
|
||||
vallist = []
|
||||
@@ -1526,7 +1533,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
def convert_url_to_book(self, url):
|
||||
book = self.make_book()
|
||||
# look here for [\d,\d] at end of url, and remove?
|
||||
mc = re.match(r"^(?P<url>.*?)(?:\[(?P<begin>\d+)?(?P<comma>,)?(?P<end>\d+)?\])?$",url)
|
||||
mc = re.match(r"^(?P<url>.*?)(?:\[(?P<begin>\d+)?(?P<comma>[,-])?(?P<end>\d+)?\])?$",url)
|
||||
#print("url:(%s) begin:(%s) end:(%s)"%(mc.group('url'),mc.group('begin'),mc.group('end')))
|
||||
url = mc.group('url')
|
||||
book['begin'] = mc.group('begin')
|
||||
|
||||
@@ -39,6 +39,7 @@ default_prefs['deleteotherforms'] = False
|
||||
default_prefs['adddialogstaysontop'] = False
|
||||
default_prefs['includeimages'] = False
|
||||
default_prefs['lookforurlinhtml'] = False
|
||||
default_prefs['checkforseriesurlid'] = True
|
||||
default_prefs['injectseries'] = False
|
||||
|
||||
default_prefs['send_lists'] = ''
|
||||
@@ -60,6 +61,13 @@ default_prefs['allow_custcol_from_ini'] = True
|
||||
|
||||
default_prefs['std_cols_newonly'] = {}
|
||||
|
||||
# This is where all preferences for this plugin *were* stored
|
||||
# Remember that this name (i.e. plugins/fanfictiondownloader_plugin) is also
|
||||
# in a global namespace, so make it as unique as possible.
|
||||
# You should always prefix your config file name with plugins/,
|
||||
# so as to ensure you dont accidentally clobber a calibre config file
|
||||
old_prefs = JSONConfig('plugins/fanfictiondownloader_plugin')
|
||||
|
||||
def set_library_config(library_config,db):
|
||||
db.prefs.set_namespaced(PREFS_NAMESPACE,
|
||||
PREFS_KEY_SETTINGS,
|
||||
@@ -73,7 +81,7 @@ def get_library_config(db):
|
||||
if library_id in old_prefs:
|
||||
#print("get prefs from old_prefs")
|
||||
library_config = old_prefs[library_id]
|
||||
set_library_config(library_config)
|
||||
set_library_config(library_config,db)
|
||||
old_prefs["migrated to library db %s"%library_id] = old_prefs[library_id]
|
||||
del old_prefs[library_id]
|
||||
|
||||
@@ -83,13 +91,6 @@ def get_library_config(db):
|
||||
copy.deepcopy(default_prefs))
|
||||
return library_config
|
||||
|
||||
# This is where all preferences for this plugin *were* stored
|
||||
# Remember that this name (i.e. plugins/fanfictiondownloader_plugin) is also
|
||||
# in a global namespace, so make it as unique as possible.
|
||||
# You should always prefix your config file name with plugins/,
|
||||
# so as to ensure you dont accidentally clobber a calibre config file
|
||||
old_prefs = JSONConfig('plugins/fanfictiondownloader_plugin')
|
||||
|
||||
# fake out so I don't have to change the prefs calls anywhere. The
|
||||
# Java programmer in me is offended by op-overloading, but it's very
|
||||
# tidy.
|
||||
|
||||
+16
-6
@@ -44,6 +44,9 @@ language_label:Language
|
||||
characters_label:Characters
|
||||
ships_label:Relationships
|
||||
series_label:Series
|
||||
seriesUrl_label:Series URL
|
||||
## seriesHTML is series as a link to seriesUrl.
|
||||
seriesHTML_label:Series
|
||||
## Completed/In-Progress
|
||||
status_label:Status
|
||||
## Dates story first published, last updated, and downloaded(last with time).
|
||||
@@ -81,7 +84,7 @@ dateUpdated_format:%%Y-%%m-%%d
|
||||
## You can include extra text or HTML that will be included as-is in
|
||||
## the title page. Eg: titlepage_entries: ...,<br />,summary,<br />,...
|
||||
## All current formats already include title and author.
|
||||
titlepage_entries: series,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
titlepage_entries: seriesHTML,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
|
||||
## Try to collect series name and number of this story in series.
|
||||
## Some sites (ab)use 'series' for reading lists and personal
|
||||
@@ -270,7 +273,7 @@ output_css:
|
||||
|
||||
[txt]
|
||||
## Add URLs since there aren't links.
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,storyUrl, authorUrl, description
|
||||
titlepage_entries: series,seriesUrl,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,storyUrl, authorUrl, description
|
||||
|
||||
## Width to word wrap text output. 0 indicates no wrapping.
|
||||
wrap_width: 78
|
||||
@@ -304,13 +307,13 @@ include_logpage: false
|
||||
## if in the list. You can include extra text or HTML that will be
|
||||
## included as-is in each log entry. Eg: logpage_entries: ...,<br />,
|
||||
## summary,<br />,...
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,title,author,description,category,genre,rating,warnings
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,series,title,author,description,category,genre,rating,warnings
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
## When using tables, make these span both columns.
|
||||
wide_titlepage_entries: description, storyUrl, author URL
|
||||
wide_titlepage_entries: description, storyUrl, authorUrl, seriesUrl
|
||||
|
||||
## output background color--only used by html and epub (and ignored in
|
||||
## epub by many readers). Included below in output_css--will be
|
||||
@@ -969,6 +972,13 @@ extracategories:InuYasha
|
||||
extracharacters:Sesshoumaru,Kagome
|
||||
extraships:Sesshoumaru/Kagome
|
||||
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
[www.dotmoon.net]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
@@ -1219,12 +1229,12 @@ extraships:Harry Potter/Ginny Weasley
|
||||
# www.squidge.org/peja calls it Fandom <shrug>
|
||||
category_label:Fandom
|
||||
# Remove numWords -- www.squidge.org/peja word counts are inaccurate
|
||||
titlepage_entries: series,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
titlepage_entries: seriesHTML,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
|
||||
[www.squidge.org/peja:txt]
|
||||
## Add URLs since there aren't links.
|
||||
# Remove numWords -- www.squidge.org/peja word counts are inaccurate
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
titlepage_entries: series,seriesUrl,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
|
||||
[www.storiesofarda.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
|
||||
+5
-2
@@ -94,6 +94,9 @@ def main(argv,
|
||||
parser.add_option("-l", "--list",
|
||||
action="store_true", dest="list",
|
||||
help="Get list of valid story URLs from page given.",)
|
||||
parser.add_option("-n", "--normalize-list",
|
||||
action="store_true", dest="normalize",default=False,
|
||||
help="Get list of valid story URLs from page given, but normalized to standard forms.",)
|
||||
parser.add_option("-d", "--debug",
|
||||
action="store_true", dest="debug",
|
||||
help="Show debug output while downloading.",)
|
||||
@@ -169,8 +172,8 @@ def main(argv,
|
||||
(var,val) = opt.split('=')
|
||||
configuration.set("overrides",var,val)
|
||||
|
||||
if options.list:
|
||||
retlist = get_urls_from_page(args[0], configuration)
|
||||
if options.list or options.normalize:
|
||||
retlist = get_urls_from_page(args[0], configuration, normalize=options.normalize)
|
||||
print "\n".join(retlist)
|
||||
|
||||
return
|
||||
|
||||
@@ -153,7 +153,7 @@ def getAdapter(config,url):
|
||||
|
||||
#logger.debug("trying url:"+url)
|
||||
(cls,fixedurl) = getClassFor(url)
|
||||
logger.debug("fixedurl:"+fixedurl)
|
||||
#logger.debug("fixedurl:"+fixedurl)
|
||||
if cls:
|
||||
adapter = cls(config,fixedurl) # raises InvalidStoryURL
|
||||
return adapter
|
||||
|
||||
@@ -195,6 +195,7 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -295,6 +295,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
series_url = 'http://'+self.host+'/fanfic/'+b['href']
|
||||
series_index = int(a.text.split(' ')[1])
|
||||
self.setSeries(series_name, series_index)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
|
||||
@@ -311,6 +311,7 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ class CastleFansOrgAdapter(BaseSiteAdapter): # XXX
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -252,6 +252,7 @@ class DarkSolaceOrgAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -218,6 +218,7 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -81,6 +81,44 @@ class DokugaComAdapter(BaseSiteAdapter):
|
||||
def getSiteURLPattern(self):
|
||||
return r"http://"+self.getSiteDomain()+"/(fanfiction|spark)?/story/\d+/?\d+?$"
|
||||
|
||||
## Login seems to be reasonably standard across eFiction sites.
|
||||
def needToLoginCheck(self, data):
|
||||
if 'The author has disabled anonymous viewing for this story.' in data:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def performLogin(self, url,soup):
|
||||
params = {}
|
||||
|
||||
if self.password:
|
||||
params['username'] = self.username
|
||||
params['passwd'] = self.password
|
||||
else:
|
||||
params['username'] = self.getConfig("username")
|
||||
params['passwd'] = self.getConfig("password")
|
||||
params['Submit'] = 'Submit'
|
||||
|
||||
# copy all hidden input tags to pick up appropriate tokens.
|
||||
for tag in soup.findAll('input',{'type':'hidden'}):
|
||||
params[tag['name']] = tag['value']
|
||||
|
||||
loginUrl = 'http://' + self.getSiteDomain() + '/fanfiction'
|
||||
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
|
||||
params['username']))
|
||||
|
||||
d = self._postUrl(loginUrl, params)
|
||||
|
||||
if "Your session has expired. Please log in again." in d:
|
||||
d = self._postUrl(loginUrl, params)
|
||||
|
||||
if "Logout" not in d : #Member Account
|
||||
logger.info("Failed to login to URL %s as %s" % (loginUrl,
|
||||
params['username']))
|
||||
raise exceptions.FailedToLogin(url,params['username'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
@@ -97,12 +135,18 @@ class DokugaComAdapter(BaseSiteAdapter):
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
soup = bs.BeautifulSoup(data)
|
||||
|
||||
if self.needToLoginCheck(data):
|
||||
# need to log in for this one.
|
||||
self.performLogin(url,soup)
|
||||
data = self._fetchUrl(url)
|
||||
soup = bs.BeautifulSoup(data)
|
||||
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
soup = bs.BeautifulSoup(data)
|
||||
# print data
|
||||
|
||||
# Now go hunting for all the meta data and the chapter list.
|
||||
@@ -218,10 +262,6 @@ class DokugaComAdapter(BaseSiteAdapter):
|
||||
a=div.text.split('Words ')
|
||||
if len(a)==2: self.story.setMetadata('numWords', a[1])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
|
||||
@@ -273,6 +273,7 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -274,6 +274,7 @@ class DramioneOrgAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -195,6 +195,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
|
||||
name=seriessoup.find('div', {'id' : 'pagetitle'})
|
||||
name.find('a').extract()
|
||||
self.setSeries(name.text.split(' by[')[0], i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
i=0
|
||||
break
|
||||
i+=1
|
||||
|
||||
@@ -280,6 +280,7 @@ class EFPFanFicNet(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId'))+'&i=1':
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -230,6 +230,7 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -132,9 +132,10 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
## For 1, use the second link.
|
||||
## For 2, fetch the crossover page and pull the two categories from there.
|
||||
|
||||
categories = soup.findAll('a',{'class':'xcontrast_txt'})
|
||||
categories = soup.find('div',{'id':'pre_story_links'}).findAll('a',{'class':'xcontrast_txt'})
|
||||
#print("xcontrast_txt a:%s"%categories)
|
||||
if len(categories) > 1:
|
||||
self.story.addToList('category',stripHTML(categories[-1]))
|
||||
self.story.addToList('category',stripHTML(categories[1]))
|
||||
elif 'Crossover' in categories[0]['href']:
|
||||
caturl = "http://%s%s"%(self.getSiteDomain(),categories[0]['href'])
|
||||
catsoup = bs.BeautifulSoup(self._fetchUrl(caturl))
|
||||
@@ -158,12 +159,17 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
if summarydiv:
|
||||
self.setDescription(url,stripHTML(summarydiv))
|
||||
|
||||
|
||||
metatext = stripHTML(gui_table1i.find('div', {'style':'color:gray;'})).replace('Hurt/Comfort','Hurt-Comfort')
|
||||
|
||||
grayspan = gui_table1i.find('span', {'class':'xgray xcontrast_txt'})
|
||||
# for b in grayspan.findAll('button'):
|
||||
# b.extract()
|
||||
metatext = stripHTML(grayspan).replace('Hurt/Comfort','Hurt-Comfort')
|
||||
#logger.debug("metatext:(%s)"%metatext)
|
||||
metalist = metatext.split(" - ")
|
||||
#logger.debug("metatext:(%s)"%metalist)
|
||||
#logger.debug("metalist:(%s)"%metalist)
|
||||
|
||||
# Rated: Fiction K - English - Words: 158,078 - Published: 02-04-11
|
||||
# Rated: Fiction T - English - Adventure/Sci-Fi - Naruto U. - Chapters: 22 - Words: 114,414 - Reviews: 395 - Favs: 779 - Follows: 835 - Updated: 03-21-13 - Published: 04-28-12 - id: 8067258
|
||||
|
||||
# rating is obtained above more robustly.
|
||||
if metalist[0].startswith('Rated:'):
|
||||
|
||||
@@ -179,7 +179,11 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
|
||||
self.setCoverImage(self.url,coverurl)
|
||||
|
||||
self.setDescription(self.url,soup.find("div", {"class":"description"}))
|
||||
# fimf has started including extra stuff inside the description div.
|
||||
descdivstr = "%s"%soup.find("div", {"class":"description"})
|
||||
hrstr="<hr />"
|
||||
descdivstr = '<div class="description">'+descdivstr[descdivstr.index(hrstr)+len(hrstr):]
|
||||
self.setDescription(self.url,descdivstr)
|
||||
|
||||
# Dates are in Unix time
|
||||
# Take the publish date from the first chapter posted
|
||||
@@ -188,7 +192,7 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
rawDateUpdated = storyMetadata["date_modified"]
|
||||
self.story.setMetadata("dateUpdated", datetime.fromtimestamp(rawDateUpdated))
|
||||
|
||||
chars = soup.find("div", {"class":"story"})
|
||||
chars = soup.find("div", {"class":"inner_data"})
|
||||
# fimfic stopped putting the char name on or around the char
|
||||
# icon now for some reason. Pull it from the image name with
|
||||
# some heuristics.
|
||||
|
||||
@@ -187,6 +187,7 @@ class FineStoriesComAdapter(BaseSiteAdapter):
|
||||
a = lc4.find('a', href=re.compile(r"/library/show_series.php\?id=\d+"))
|
||||
i = a.parent.text.split('(')[1].split(')')[0]
|
||||
self.setSeries(a.text, i)
|
||||
self.story.setMetadata('seriesUrl','http://'+self.host+a['href'])
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
|
||||
@@ -275,6 +275,7 @@ class GrangerEnchantedCom(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ class HLFictionNetAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -264,6 +264,7 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ class JLAUnlimitedComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ class LibraryOfMoriaComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -260,6 +260,7 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -306,6 +306,7 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
|
||||
|
||||
if 'Series' in label:
|
||||
self.setSeries(stripHTML(value.nextSibling), value.nextSibling.nextSibling.string[2:])
|
||||
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+value.nextSibling['href'])
|
||||
|
||||
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
|
||||
story=asoup.find('a', href=re.compile(r'viewstory.php\?storyid='+self.story.getMetadata('storyId')))
|
||||
|
||||
@@ -164,6 +164,7 @@ class NCISFicComAdapter(BaseSiteAdapter):
|
||||
if 'Series' in label:
|
||||
if "No Series" not in value.nextSibling.string:
|
||||
self.setSeries(stripHTML(value.nextSibling), value.nextSibling.nextSibling.string[2:])
|
||||
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+value.nextSibling['href'])
|
||||
|
||||
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
|
||||
story=asoup.find('a', href=re.compile(r'viewstory.php\?storyid='+self.story.getMetadata('storyId')))
|
||||
|
||||
@@ -193,7 +193,7 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
|
||||
series_name = a.find('a').string
|
||||
i = a.text.split('#')[1]
|
||||
self.setSeries(series_name, i)
|
||||
|
||||
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+a.find('a')['href'])
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
pass
|
||||
|
||||
@@ -264,6 +264,7 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -275,6 +275,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ class PotionsAndSnitchesNetSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -228,6 +228,7 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ class PrisonBreakFicNetAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ class PsychFicComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ class QafFicComAdapter(BaseSiteAdapter):
|
||||
name=seriessoup.find('div', {'id' : 'pagetitle'})
|
||||
name.find('a').extract()
|
||||
self.setSeries(name.text.split(' by[')[0], i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
i=0
|
||||
break
|
||||
i+=1
|
||||
|
||||
@@ -207,6 +207,7 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -233,6 +233,7 @@ class SG1HeliopolisComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -226,6 +226,7 @@ class SinfulDesireOrgAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -215,6 +215,7 @@ class SiyeCoUkAdapter(BaseSiteAdapter): # XXX
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ class SquidgeOrgPejaAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ class StargateAtlantisOrgAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -249,6 +249,7 @@ class SVUFictionComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ class TenhawkPresentsComSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -101,8 +101,10 @@ Some more longer description. "I suck at summaries!" "Better than it sounds!"
|
||||
}
|
||||
self.story.setMetadata('language',langs[idnum%len(langs)])
|
||||
self.setSeries('The Great Test',idnum)
|
||||
self.story.setMetadata('seriesUrl','http://test1.com?seriesid=1')
|
||||
if idnum == 0:
|
||||
self.setSeries("A Nook Hyphen Test "+self.story.getMetadata('dateCreated'),idnum)
|
||||
self.story.setMetadata('seriesUrl','http://test1.com?seriesid=0')
|
||||
|
||||
self.story.setMetadata('rating','Tweenie')
|
||||
|
||||
@@ -126,21 +128,36 @@ Some more longer description. "I suck at summaries!" "Better than it sounds!"
|
||||
self.story.addToList('warnings','Swearing')
|
||||
self.story.addToList('warnings','Violence')
|
||||
|
||||
self.story.addToList('category','Harry Potter')
|
||||
self.story.addToList('category','Furbie')
|
||||
self.story.addToList('category','Crossover')
|
||||
self.story.addToList('category',u'Puella Magi Madoka Magica/魔法少女まどか★マギカ')
|
||||
self.story.addToList('category',u'Magical Girl Lyrical Nanoha')
|
||||
|
||||
if self.story.getMetadata('storyId') == '80':
|
||||
self.story.addToList('category',u'Rizzoli & Isles')
|
||||
self.story.addToList('characters','J. Rizzoli')
|
||||
elif self.story.getMetadata('storyId') == '81':
|
||||
self.story.addToList('category',u'Pitch Perfect')
|
||||
self.story.addToList('characters','Chloe B.')
|
||||
elif self.story.getMetadata('storyId') == '83':
|
||||
self.story.addToList('category',u'Rizzoli & Isles')
|
||||
self.story.addToList('characters','J. Rizzoli')
|
||||
self.story.addToList('category',u'Pitch Perfect')
|
||||
self.story.addToList('characters','Chloe B.')
|
||||
elif self.story.getMetadata('storyId') == '82':
|
||||
self.story.addToList('characters','Henry (Once Upon a Time)')
|
||||
self.story.addToList('category',u'Once Upon a Time (TV)')
|
||||
else:
|
||||
self.story.addToList('category','Harry Potter')
|
||||
self.story.addToList('category','Furbie')
|
||||
self.story.addToList('category','Crossover')
|
||||
self.story.addToList('category',u'Puella Magi Madoka Magica/魔法少女まどか★マギカ')
|
||||
self.story.addToList('category',u'Magical Girl Lyrical Nanoha')
|
||||
self.story.addToList('category',u'Once Upon a Time (TV)')
|
||||
self.story.addToList('characters','Bob Smith')
|
||||
self.story.addToList('characters','George Johnson')
|
||||
self.story.addToList('characters','Fred Smythe')
|
||||
|
||||
self.story.addToList('genre','Fantasy')
|
||||
self.story.addToList('genre','Comedy')
|
||||
self.story.addToList('genre','Sci-Fi')
|
||||
self.story.addToList('genre','Noir')
|
||||
|
||||
self.story.addToList('characters','Bob Smith')
|
||||
self.story.addToList('characters','George Johnson')
|
||||
self.story.addToList('characters','Fred Smythe')
|
||||
|
||||
|
||||
self.story.addToList('listX','xVal1')
|
||||
self.story.addToList('listX','xVal2')
|
||||
self.story.addToList('listX','xVal3')
|
||||
|
||||
@@ -189,6 +189,7 @@ class TheAlphaGateComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -283,6 +283,7 @@ class TheHookupZoneNetAdapter(BaseSiteAdapter): # XXX
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -263,6 +263,7 @@ class TheQuidditchPitchOrgAdapter(BaseSiteAdapter): # XXX
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ class TheWritersCoffeeShopComSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -163,17 +163,44 @@ class TwistingTheHellmouthSiteAdapter(BaseSiteAdapter):
|
||||
infodata = self._fetchUrl(infourl)
|
||||
infosoup = bs.BeautifulSoup(infodata)
|
||||
|
||||
for a in infosoup.findAll('a',href=re.compile(r"^/Author-\d+")):
|
||||
self.story.addToList('authorId',a['href'].split('/')[1].split('-')[1])
|
||||
self.story.addToList('authorUrl','http://'+self.host+a['href'].replace("/Author-","/AuthorStories-"))
|
||||
self.story.addToList('author',stripHTML(a))
|
||||
# for a in infosoup.findAll('a',href=re.compile(r"^/Author-\d+")):
|
||||
# self.story.addToList('authorId',a['href'].split('/')[1].split('-')[1])
|
||||
# self.story.addToList('authorUrl','http://'+self.host+a['href'].replace("/Author-","/AuthorStories-"))
|
||||
# self.story.addToList('author',stripHTML(a))
|
||||
|
||||
# second verticaltable is the chapter list.
|
||||
table = infosoup.findAll('table',{'class':'verticaltable'})[1]
|
||||
for a in table.findAll('a',href=re.compile(r"^/Story-"+self.story.getMetadata('storyId'))):
|
||||
autha = a.findNext('a',href=re.compile(r"^/Author-\d+"))
|
||||
self.story.addToList('authorId',autha['href'].split('/')[1].split('-')[1])
|
||||
self.story.addToList('authorUrl','http://'+self.host+autha['href'].replace("/Author-","/AuthorStories-"))
|
||||
self.story.addToList('author',stripHTML(autha))
|
||||
# include leading number to match 1. ... 2. ...
|
||||
self.chapterUrls.append(("%d. %s by %s"%(len(self.chapterUrls)+1,
|
||||
stripHTML(a),
|
||||
stripHTML(autha)),'http://'+self.host+a['href']))
|
||||
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(url)
|
||||
else:
|
||||
raise e
|
||||
else: # single author:
|
||||
# Find the chapter selector
|
||||
select = soup.find('select', { 'name' : 'chapnav' } )
|
||||
|
||||
if select is None:
|
||||
# no selector found, so it's a one-chapter story.
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
allOptions = select.findAll('option')
|
||||
for o in allOptions:
|
||||
url = "http://"+self.host+o['value']
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(o),url))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
try:
|
||||
# going to pull part of the meta data from *primary* author list page.
|
||||
logger.debug("**AUTHOR** URL: "+authorurl)
|
||||
@@ -235,29 +262,12 @@ class TwistingTheHellmouthSiteAdapter(BaseSiteAdapter):
|
||||
if BtVS:
|
||||
self.story.addToList('category','Buffy: The Vampire Slayer')
|
||||
|
||||
# Find the chapter selector
|
||||
select = soup.find('select', { 'name' : 'chapnav' } )
|
||||
|
||||
if select is None:
|
||||
# no selector found, so it's a one-chapter story.
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
allOptions = select.findAll('option')
|
||||
for o in allOptions:
|
||||
url = "http://"+self.host+o['value']
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(o),url))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
pseries = soup.find('p', {'style':'margin-top:0px'})
|
||||
m = re.match('This story is No\. (?P<num>\d+) in the series "(?P<series>.+)"\.',
|
||||
pseries.text)
|
||||
if m:
|
||||
self.setSeries(m.group('series'),m.group('num'))
|
||||
|
||||
return
|
||||
|
||||
self.story.setMetadata('seriesUrl',"http://"+self.host+pseries.find('a')['href'])
|
||||
|
||||
def getChapterText(self, url):
|
||||
logger.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
@@ -163,6 +163,7 @@ class TwilightArchivesComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('/read/'+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -217,6 +217,7 @@ class TwilightedNetSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ class TwiwriteNetSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ class WalkingThePlankOrgAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ class WhoficComSiteAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -277,6 +277,7 @@ class WizardTalesNetAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ class WolverineAndRogueComAdapter(BaseSiteAdapter):
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -126,8 +126,13 @@ class WraithBaitComAdapter(BaseSiteAdapter):
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
# include author on chapters if multiple authors.
|
||||
if len(alist) > 1:
|
||||
add = " by %s"%stripHTML(chapter.findNext('a', href=re.compile(r"viewuser.php\?uid=\d+")))
|
||||
else:
|
||||
add = ""
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['href']+addurl))
|
||||
self.chapterUrls.append((stripHTML(chapter)+add,'http://'+self.host+'/'+chapter['href']+addurl))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
@@ -196,6 +201,7 @@ class WraithBaitComAdapter(BaseSiteAdapter):
|
||||
for a in storyas:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
self.story.setMetadata('seriesUrl',series_url)
|
||||
break
|
||||
i+=1
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class Configuration(ConfigParser.SafeConfigParser):
|
||||
'characters',
|
||||
'ships',
|
||||
'series',
|
||||
'seriesUrl',
|
||||
'status',
|
||||
'datePublished',
|
||||
'dateUpdated',
|
||||
@@ -75,6 +76,7 @@ class Configuration(ConfigParser.SafeConfigParser):
|
||||
'langcode',
|
||||
'output_css',
|
||||
'authorHTML',
|
||||
'seriesHTML',
|
||||
'lastupdate'
|
||||
]
|
||||
|
||||
|
||||
+16
-10
@@ -25,7 +25,7 @@ from gziphttp import GZipProcessor
|
||||
import adapters
|
||||
from configurable import Configuration
|
||||
|
||||
def get_urls_from_page(url,configuration=None):
|
||||
def get_urls_from_page(url,configuration=None,normalize=False):
|
||||
|
||||
if not configuration:
|
||||
configuration = Configuration("test1.com","EPUB")
|
||||
@@ -54,11 +54,11 @@ def get_urls_from_page(url,configuration=None):
|
||||
opener = u2.build_opener(u2.HTTPCookieProcessor(),GZipProcessor())
|
||||
data = opener.open(url).read()
|
||||
|
||||
return get_urls_from_html(data,url)
|
||||
return get_urls_from_html(data,url,configuration,normalize)
|
||||
|
||||
def get_urls_from_html(data,url=None,configuration=None):
|
||||
def get_urls_from_html(data,url=None,configuration=None,normalize=False):
|
||||
|
||||
normalized = set() # normalized url
|
||||
normalized = [] # normalized url
|
||||
retlist = [] # orig urls.
|
||||
|
||||
if not configuration:
|
||||
@@ -82,16 +82,19 @@ def get_urls_from_html(data,url=None,configuration=None):
|
||||
href = href.replace('&index=1','')
|
||||
adapter = adapters.getAdapter(configuration,href)
|
||||
if adapter.story.getMetadata('storyUrl') not in normalized:
|
||||
normalized.add(adapter.story.getMetadata('storyUrl'))
|
||||
normalized.append(adapter.story.getMetadata('storyUrl'))
|
||||
retlist.append(href)
|
||||
except:
|
||||
pass
|
||||
|
||||
return retlist
|
||||
if normalize:
|
||||
return normalized
|
||||
else:
|
||||
return retlist
|
||||
|
||||
def get_urls_from_text(data,configuration=None):
|
||||
def get_urls_from_text(data,configuration=None,normalize=False):
|
||||
|
||||
normalized = set() # normalized url
|
||||
normalized = [] # normalized url
|
||||
retlist = [] # orig urls.
|
||||
|
||||
if not configuration:
|
||||
@@ -109,12 +112,15 @@ def get_urls_from_text(data,configuration=None):
|
||||
href = href.replace('&index=1','')
|
||||
adapter = adapters.getAdapter(configuration,href)
|
||||
if adapter.story.getMetadata('storyUrl') not in normalized:
|
||||
normalized.add(adapter.story.getMetadata('storyUrl'))
|
||||
normalized.append(adapter.story.getMetadata('storyUrl'))
|
||||
retlist.append(href)
|
||||
except:
|
||||
pass
|
||||
|
||||
return retlist
|
||||
if normalize:
|
||||
return normalized
|
||||
else:
|
||||
return retlist
|
||||
|
||||
def form_url(parenturl,url):
|
||||
url = url.strip() # ran across an image with a space in the
|
||||
|
||||
@@ -352,7 +352,7 @@ class Story(Configurable):
|
||||
allmetadata = {}
|
||||
|
||||
# special handling for authors/authorUrls
|
||||
authlinkhtml="<a class='authorlink' href='%s'>%s</a>"
|
||||
linkhtml="<a class='%slink' href='%s'>%s</a>"
|
||||
if self.isList('author'): # more than one author, assume multiple authorUrl too.
|
||||
htmllist=[]
|
||||
for i, v in enumerate(self.getList('author')):
|
||||
@@ -366,12 +366,18 @@ class Story(Configurable):
|
||||
aurl=removeAllEntities(aurl)
|
||||
auth=removeAllEntities(auth)
|
||||
|
||||
htmllist.append(authlinkhtml%(aurl,auth))
|
||||
htmllist.append(linkhtml%('author',aurl,auth))
|
||||
self.setMetadata('authorHTML',', '.join(htmllist))
|
||||
else:
|
||||
self.setMetadata('authorHTML',authlinkhtml%(self.getMetadata('authorUrl', removeallentities, doreplacements),
|
||||
self.getMetadata('author', removeallentities, doreplacements)))
|
||||
self.setMetadata('authorHTML',linkhtml%('author',self.getMetadata('authorUrl', removeallentities, doreplacements),
|
||||
self.getMetadata('author', removeallentities, doreplacements)))
|
||||
|
||||
if self.getMetadataRaw('seriesUrl') != None:
|
||||
self.setMetadata('seriesHTML',linkhtml%('series',self.getMetadata('seriesUrl', removeallentities, doreplacements),
|
||||
self.getMetadata('series', removeallentities, doreplacements)))
|
||||
elif self.getMetadataRaw('series') != None:
|
||||
self.setMetadata('seriesHTML',self.getMetadataRaw('series'))
|
||||
|
||||
for k in self.getValidMetaList():
|
||||
if self.isList(k) and keeplists:
|
||||
allmetadata[k] = self.getList(k, removeallentities, doreplacements)
|
||||
@@ -415,11 +421,13 @@ class Story(Configurable):
|
||||
# includelist prevents infinite recursion of include_in_'s
|
||||
if self.hasConfig("include_in_"+listname) and listname not in includelist:
|
||||
for k in self.getConfigList("include_in_"+listname):
|
||||
retlist.extend(self.getList(k,removeallentities,doreplacements,includelist=includelist+[listname]))
|
||||
retlist.extend(self.getList(k,removeallentities=False,
|
||||
doreplacements=doreplacements,includelist=includelist+[listname]))
|
||||
else:
|
||||
|
||||
if not self.isList(listname):
|
||||
retlist = [self.getMetadata(listname,removeallentities, doreplacements)]
|
||||
retlist = [self.getMetadata(listname,removeallentities=False,
|
||||
doreplacements=doreplacements)]
|
||||
else:
|
||||
retlist = self.getMetadataRaw(listname)
|
||||
|
||||
|
||||
+4
-2
@@ -57,7 +57,9 @@
|
||||
<h3>Changes:</h3>
|
||||
<p>
|
||||
<ul>
|
||||
<li>Change how fimfiction.net story descriptions are collected.</li>
|
||||
<li>Another fix for fanfiction.net changes.</li>
|
||||
<li>Add author to chapter TOC for multi-author stories on TtH and WraithBait.<br />
|
||||
(AO3 doesn't reliably report chapter author.)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
@@ -68,7 +70,7 @@
|
||||
If you have any problems with this application, please
|
||||
report them in
|
||||
the <a href="http://groups.google.com/group/fanfic-downloader">FanFictionDownLoader Google Group</a>. The
|
||||
<a href="http://4-4-45.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
<a href="http://4-4-51.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
</p>
|
||||
<div id='error'>
|
||||
{{ error_message }}
|
||||
|
||||
+16
-6
@@ -44,6 +44,9 @@ language_label:Language
|
||||
characters_label:Characters
|
||||
ships_label:Relationships
|
||||
series_label:Series
|
||||
seriesUrl_label:Series URL
|
||||
## seriesHTML is series as a link to seriesUrl.
|
||||
seriesHTML_label:Series
|
||||
## Completed/In-Progress
|
||||
status_label:Status
|
||||
## Dates story first published, last updated, and downloaded(last with time).
|
||||
@@ -81,7 +84,7 @@ dateUpdated_format:%%Y-%%m-%%d
|
||||
## You can include extra text or HTML that will be included as-is in
|
||||
## the title page. Eg: titlepage_entries: ...,<br />,summary,<br />,...
|
||||
## All current formats already include title and author.
|
||||
titlepage_entries: series,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
titlepage_entries: seriesHTML,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
|
||||
## Try to collect series name and number of this story in series.
|
||||
## Some sites (ab)use 'series' for reading lists and personal
|
||||
@@ -249,7 +252,7 @@ output_css:
|
||||
|
||||
[txt]
|
||||
## Add URLs since there aren't links.
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,storyUrl, authorUrl, description
|
||||
titlepage_entries: series,seriesUrl,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,storyUrl, authorUrl, description
|
||||
|
||||
## Width to word wrap text output. 0 indicates no wrapping.
|
||||
wrap_width: 78
|
||||
@@ -282,13 +285,13 @@ include_logpage: false
|
||||
## if in the list. You can include extra text or HTML that will be
|
||||
## included as-is in each log entry. Eg: logpage_entries: ...,<br />,
|
||||
## summary,<br />,...
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,title,author,description,category,genre,rating,warnings
|
||||
logpage_entries: dateCreated,datePublished,dateUpdated,numChapters,numWords,status,series,title,author,description,category,genre,rating,warnings
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
## When using tables, make these span both columns.
|
||||
wide_titlepage_entries: description, storyUrl, author URL
|
||||
wide_titlepage_entries: description, storyUrl, authorUrl, seriesUrl
|
||||
|
||||
## output background color--only used by html and epub (and ignored in
|
||||
## epub by many readers). Included below in output_css--will be
|
||||
@@ -951,6 +954,13 @@ extracategories:InuYasha
|
||||
extracharacters:Sesshoumaru,Kagome
|
||||
extraships:Sesshoumaru/Kagome
|
||||
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
[www.dotmoon.net]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
@@ -1198,12 +1208,12 @@ extraships:Harry Potter/Ginny Weasley
|
||||
# www.squidge.org/peja calls it Fandom <shrug>
|
||||
category_label:Fandom
|
||||
# Remove numWords -- www.squidge.org/peja word counts are inaccurate
|
||||
titlepage_entries: series,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
titlepage_entries: seriesHTML,category,genre,language,characters,ships,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
|
||||
[www.squidge.org/peja:txt]
|
||||
## Add URLs since there aren't links.
|
||||
# Remove numWords -- www.squidge.org/peja word counts are inaccurate
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
titlepage_entries: series,seriesUrl,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
|
||||
[www.storiesofarda.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
|
||||
Reference in New Issue
Block a user