mirror of
https://github.com/wassname/FanFicFare.git
synced 2026-09-15 11:36:30 +08:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
079a0f5906 | ||
|
|
f31fcc3341 | ||
|
|
7eb1685be1 | ||
|
|
30997053ef | ||
|
|
347eb69044 | ||
|
|
86329a1fe3 | ||
|
|
3bcc31a404 | ||
|
|
5e7c28d2dc | ||
|
|
574981931f | ||
|
|
a54765cf86 | ||
|
|
7cd77c64d1 | ||
|
|
fd68b32111 | ||
|
|
eb969cfe2f | ||
|
|
337da79dbc | ||
|
|
3176b4944b | ||
|
|
a18c52e5a3 | ||
|
|
1cfaff9d42 | ||
|
|
793c65495f | ||
|
|
efb908d438 | ||
|
|
6ea015b111 | ||
|
|
eab986a07a | ||
|
|
89b86545e0 | ||
|
|
3543eecfcc | ||
|
|
dc2370fed9 | ||
|
|
f83d9467fc | ||
|
|
a82a40a221 | ||
|
|
cbc2605804 | ||
|
|
6c6a84f533 | ||
|
|
1be2e50beb | ||
|
|
5c37bddca9 | ||
|
|
42473d4f1d | ||
|
|
850567afde |
@@ -1,6 +1,6 @@
|
||||
# ffd-retief-hrd fanfictiondownloader
|
||||
application: fanfictiondownloader
|
||||
version: 4-4-21
|
||||
version: 4-4-24
|
||||
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, 6, 0)
|
||||
version = (1, 6, 8)
|
||||
minimum_calibre_version = (0, 8, 57)
|
||||
|
||||
#: This field defines the GUI plugin class that contains all the code
|
||||
|
||||
+117
-27
@@ -8,6 +8,7 @@ __copyright__ = '2012, Jim Miller'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import traceback, copy
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt4.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QFont, QWidget,
|
||||
QTextEdit, QComboBox, QCheckBox, QPushButton, QTabWidget, QVariant, QScrollArea)
|
||||
@@ -62,6 +63,9 @@ default_prefs['countpagesstats'] = []
|
||||
|
||||
default_prefs['errorcol'] = ''
|
||||
default_prefs['custom_cols'] = {}
|
||||
default_prefs['custom_cols_newonly'] = {}
|
||||
|
||||
default_prefs['std_cols_newonly'] = {}
|
||||
|
||||
def set_library_config(library_config):
|
||||
get_gui().current_db.prefs.set_namespaced(PREFS_NAMESPACE,
|
||||
@@ -75,14 +79,14 @@ def get_library_config():
|
||||
# Check whether this is a configuration needing to be migrated
|
||||
# from json into database. If so: get it, set it, rename it in json.
|
||||
if library_id in old_prefs:
|
||||
print("get prefs from old_prefs")
|
||||
#print("get prefs from old_prefs")
|
||||
library_config = old_prefs[library_id]
|
||||
set_library_config(library_config)
|
||||
old_prefs["migrated to library db %s"%library_id] = old_prefs[library_id]
|
||||
del old_prefs[library_id]
|
||||
|
||||
if library_config is None:
|
||||
print("get prefs from db")
|
||||
#print("get prefs from db")
|
||||
library_config = db.prefs.get_namespaced(PREFS_NAMESPACE, PREFS_KEY_SETTINGS,
|
||||
copy.deepcopy(default_prefs))
|
||||
return library_config
|
||||
@@ -106,7 +110,7 @@ class PrefsFacade():
|
||||
def _get_prefs(self):
|
||||
libraryid = get_library_uuid(get_gui().current_db)
|
||||
if self.current_prefs == None or self.libraryid != libraryid:
|
||||
print("self.current_prefs == None(%s) or self.libraryid != libraryid(%s)"%(self.current_prefs == None,self.libraryid != libraryid))
|
||||
#print("self.current_prefs == None(%s) or self.libraryid != libraryid(%s)"%(self.current_prefs == None,self.libraryid != libraryid))
|
||||
self.libraryid = libraryid
|
||||
self.current_prefs = get_library_config()
|
||||
return self.current_prefs
|
||||
@@ -172,8 +176,11 @@ class ConfigWidget(QWidget):
|
||||
if 'Count Pages' not in plugin_action.gui.iactions:
|
||||
self.countpages_tab.setEnabled(False)
|
||||
|
||||
self.columns_tab = CustomColumnsTab(self, plugin_action)
|
||||
tab_widget.addTab(self.columns_tab, 'Custom Columns')
|
||||
self.std_columns_tab = StandardColumnsTab(self, plugin_action)
|
||||
tab_widget.addTab(self.std_columns_tab, 'Standard Columns')
|
||||
|
||||
self.cust_columns_tab = CustomColumnsTab(self, plugin_action)
|
||||
tab_widget.addTab(self.cust_columns_tab, 'Custom Columns')
|
||||
|
||||
self.other_tab = OtherTab(self, plugin_action)
|
||||
tab_widget.addTab(self.other_tab, 'Other')
|
||||
@@ -241,19 +248,30 @@ class ConfigWidget(QWidget):
|
||||
|
||||
prefs['countpagesstats'] = countpagesstats
|
||||
|
||||
# Standard Columns tab
|
||||
colsnewonly = {}
|
||||
for (col,checkbox) in self.std_columns_tab.stdcol_newonlycheck.iteritems():
|
||||
colsnewonly[col] = checkbox.isChecked()
|
||||
prefs['std_cols_newonly'] = colsnewonly
|
||||
|
||||
# Custom Columns tab
|
||||
# error column
|
||||
prefs['errorcol'] = unicode(self.columns_tab.errorcol.itemData(self.columns_tab.errorcol.currentIndex()).toString())
|
||||
prefs['errorcol'] = unicode(self.cust_columns_tab.errorcol.itemData(self.cust_columns_tab.errorcol.currentIndex()).toString())
|
||||
|
||||
# cust cols
|
||||
colsmap = {}
|
||||
for (col,combo) in self.columns_tab.custcol_dropdowns.iteritems():
|
||||
for (col,combo) in self.cust_columns_tab.custcol_dropdowns.iteritems():
|
||||
val = unicode(combo.itemData(combo.currentIndex()).toString())
|
||||
if val != 'none':
|
||||
colsmap[col] = val
|
||||
#print("colsmap[%s]:%s"%(col,colsmap[col]))
|
||||
prefs['custom_cols'] = colsmap
|
||||
|
||||
colsnewonly = {}
|
||||
for (col,checkbox) in self.cust_columns_tab.custcol_newonlycheck.iteritems():
|
||||
colsnewonly[col] = checkbox.isChecked()
|
||||
prefs['custom_cols_newonly'] = colsnewonly
|
||||
|
||||
prefs.save_to_db()
|
||||
|
||||
def edit_shortcuts(self):
|
||||
@@ -313,7 +331,7 @@ class BasicTab(QWidget):
|
||||
self.l.addLayout(horz)
|
||||
|
||||
self.updatemeta = QCheckBox('Default Update Calibre &Metadata?',self)
|
||||
self.updatemeta.setToolTip("On each download, FFDL offers an option to update Calibre's metadata (title, author, URL, tags, custom columns, etc) from the web site. <br />This sets whether that will default to on or off.")
|
||||
self.updatemeta.setToolTip("On each download, FFDL offers an option to update Calibre's metadata (title, author, URL, tags, custom columns, etc) from the web site. <br />This sets whether that will default to on or off. <br />Columns set to 'New Only' in the column tabs will only be set for new books.")
|
||||
self.updatemeta.setChecked(prefs['updatemeta'])
|
||||
self.l.addWidget(self.updatemeta)
|
||||
|
||||
@@ -322,16 +340,25 @@ class BasicTab(QWidget):
|
||||
self.updateepubcover.setChecked(prefs['updateepubcover'])
|
||||
self.l.addWidget(self.updateepubcover)
|
||||
|
||||
self.l.addSpacing(10)
|
||||
|
||||
self.deleteotherforms = QCheckBox('Delete other existing formats?',self)
|
||||
self.deleteotherforms.setToolTip('Check this to automatically delete all other ebook formats when updating an existing book.\nHandy if you have both a Nook(epub) and Kindle(mobi), for example.')
|
||||
self.deleteotherforms.setChecked(prefs['deleteotherforms'])
|
||||
self.l.addWidget(self.deleteotherforms)
|
||||
|
||||
self.updatecover = QCheckBox('Update Calibre Cover when Updating Metadata?',self)
|
||||
self.updatecover.setToolTip("Update calibre book cover image from EPUB when metadata is updated. (EPUB only.)\nDoesn't go looking for new images on 'Update Calibre Metadata Only'.")
|
||||
self.updatecover.setChecked(prefs['updatecover'])
|
||||
self.l.addWidget(self.updatecover)
|
||||
|
||||
self.keeptags = QCheckBox('Keep Existing Tags when Updating Metadata?',self)
|
||||
self.keeptags.setToolTip('Existing tags will be kept and any new tags added.\nCompleted and In-Progress tags will be still be updated, if known.\nLast Updated tags will be updated if lastupdate in include_subject_tags.')
|
||||
self.keeptags.setToolTip("Existing tags will be kept and any new tags added.\nCompleted and In-Progress tags will be still be updated, if known.\nLast Updated tags will be updated if lastupdate in include_subject_tags.\n(If Tags is set to 'New Only' in the Standard Columns tab, this has no effect.)")
|
||||
self.keeptags.setChecked(prefs['keeptags'])
|
||||
self.l.addWidget(self.keeptags)
|
||||
|
||||
self.l.addSpacing(10)
|
||||
|
||||
self.urlsfromclip = QCheckBox('Take URLs from Clipboard?',self)
|
||||
self.urlsfromclip.setToolTip('Prefill URLs from valid URLs in Clipboard when Adding New.')
|
||||
self.urlsfromclip.setChecked(prefs['urlsfromclip'])
|
||||
@@ -343,16 +370,13 @@ class BasicTab(QWidget):
|
||||
self.updatedefault.setChecked(prefs['updatedefault'])
|
||||
self.l.addWidget(self.updatedefault)
|
||||
|
||||
self.deleteotherforms = QCheckBox('Delete other existing formats?',self)
|
||||
self.deleteotherforms.setToolTip('Check this to automatically delete all other ebook formats when updating an existing book.\nHandy if you have both a Nook(epub) and Kindle(mobi), for example.')
|
||||
self.deleteotherforms.setChecked(prefs['deleteotherforms'])
|
||||
self.l.addWidget(self.deleteotherforms)
|
||||
|
||||
self.adddialogstaysontop = QCheckBox("Keep 'Add New from URL(s)' dialog on top?",self)
|
||||
self.adddialogstaysontop.setToolTip("Instructs the OS and Window Manager to keep the 'Add New from URL(s)'\ndialog on top of all other windows. Useful for dragging URLs onto it.")
|
||||
self.adddialogstaysontop.setChecked(prefs['adddialogstaysontop'])
|
||||
self.l.addWidget(self.adddialogstaysontop)
|
||||
|
||||
self.l.addSpacing(10)
|
||||
|
||||
# this is a cheat to make it easier for users to realize there's a new include_images features.
|
||||
self.includeimages = QCheckBox("Include images in EPUBs?",self)
|
||||
self.includeimages.setToolTip("Download and include images in EPUB stories. This is equivalent to adding:\n\n[epub]\ninclude_images:true\nkeep_summary_html:true\nmake_firstimage_cover:true\n\n ...to the top of personal.ini. Your settings in personal.ini will override this.")
|
||||
@@ -604,27 +628,27 @@ class CountPagesTab(QWidget):
|
||||
self.l.addSpacing(5)
|
||||
|
||||
# 'PageCount', 'WordCount', 'FleschReading', 'FleschGrade', 'GunningFog'
|
||||
self.pagecount = QCheckBox('PageCount',self)
|
||||
self.pagecount = QCheckBox('Page Count',self)
|
||||
self.pagecount.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.pagecount.setChecked('PageCount' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.pagecount)
|
||||
|
||||
self.wordcount = QCheckBox('WordCount',self)
|
||||
self.wordcount = QCheckBox('Word Count',self)
|
||||
self.wordcount.setToolTip('Which column and algorithm to use are configured in Count Words.\nWill overwrite word count from FFDL metadata if set to update the same custom column.')
|
||||
self.wordcount.setChecked('WordCount' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.wordcount)
|
||||
|
||||
self.fleschreading = QCheckBox('FleschReading',self)
|
||||
self.fleschreading = QCheckBox('Flesch Reading Ease',self)
|
||||
self.fleschreading.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.fleschreading.setChecked('FleschReading' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.fleschreading)
|
||||
|
||||
self.fleschgrade = QCheckBox('Fleschgrade',self)
|
||||
self.fleschgrade = QCheckBox('Flesch-Kincaid Grade Level',self)
|
||||
self.fleschgrade.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.fleschgrade.setChecked('Fleschgrade' in prefs['countpagesstats'])
|
||||
self.fleschgrade.setChecked('FleschGrade' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.fleschgrade)
|
||||
|
||||
self.gunningfog = QCheckBox('GunningFog',self)
|
||||
self.gunningfog = QCheckBox('Gunning Fog Index',self)
|
||||
self.gunningfog.setToolTip('Which column and algorithm to use are configured in Count Pages.')
|
||||
self.gunningfog.setChecked('GunningFog' in prefs['countpagesstats'])
|
||||
self.l.addWidget(self.gunningfog)
|
||||
@@ -691,6 +715,7 @@ permitted_values = {
|
||||
'language',
|
||||
'series',
|
||||
'characters',
|
||||
'ships',
|
||||
'status',
|
||||
'datePublished',
|
||||
'dateUpdated',
|
||||
@@ -727,6 +752,7 @@ titleLabels = {
|
||||
'status-I':'Status:In-Progress',
|
||||
'series':'Series',
|
||||
'characters':'Characters',
|
||||
'ships':'Relationships',
|
||||
'datePublished':'Published',
|
||||
'dateUpdated':'Updated',
|
||||
'dateCreated':'Packaged',
|
||||
@@ -767,7 +793,17 @@ class CustomColumnsTab(QWidget):
|
||||
self.l.addSpacing(5)
|
||||
|
||||
self.custcol_dropdowns = {}
|
||||
self.custcol_newonlycheck = {}
|
||||
|
||||
scrollable = QScrollArea()
|
||||
scrollcontent = QWidget()
|
||||
scrollable.setWidget(scrollcontent)
|
||||
scrollable.setWidgetResizable(True)
|
||||
self.l.addWidget(scrollable)
|
||||
|
||||
self.sl = QVBoxLayout()
|
||||
scrollcontent.setLayout(self.sl)
|
||||
|
||||
for key, column in custom_columns.iteritems():
|
||||
|
||||
if column['datatype'] in permitted_values:
|
||||
@@ -775,8 +811,8 @@ class CustomColumnsTab(QWidget):
|
||||
# for (k,v) in column.iteritems():
|
||||
# print("column['%s'] => %s"%(k,v))
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel('%s(%s)'%(column['name'],key))
|
||||
label.setToolTip("Update this %s column with..."%column['datatype'])
|
||||
label = QLabel(column['name'])
|
||||
label.setToolTip("Update this %s column(%s) with..."%(key,column['datatype']))
|
||||
horz.addWidget(label)
|
||||
dropdown = QComboBox(self)
|
||||
dropdown.addItem('',QVariant('none'))
|
||||
@@ -789,11 +825,18 @@ class CustomColumnsTab(QWidget):
|
||||
dropdown.setToolTip("Metadata values valid for this type of column.\nValues that aren't valid for this enumeration column will be ignored.")
|
||||
else:
|
||||
dropdown.setToolTip("Metadata values valid for this type of column.")
|
||||
|
||||
horz.addWidget(dropdown)
|
||||
self.l.addLayout(horz)
|
||||
|
||||
newonlycheck = QCheckBox("New Only",self)
|
||||
newonlycheck.setToolTip("Write to %s(%s) only for new\nbooks, not updates to existing books."%(column['name'],key))
|
||||
self.custcol_newonlycheck[key] = newonlycheck
|
||||
if key in prefs['custom_cols_newonly']:
|
||||
newonlycheck.setChecked(prefs['custom_cols_newonly'][key])
|
||||
horz.addWidget(newonlycheck)
|
||||
|
||||
self.sl.addLayout(horz)
|
||||
|
||||
self.l.insertStretch(-1)
|
||||
self.sl.insertStretch(-1)
|
||||
|
||||
self.l.addSpacing(5)
|
||||
label = QLabel("Special column:")
|
||||
@@ -815,6 +858,53 @@ class CustomColumnsTab(QWidget):
|
||||
self.errorcol.setCurrentIndex(self.errorcol.findData(QVariant(prefs['errorcol'])))
|
||||
horz.addWidget(self.errorcol)
|
||||
self.l.addLayout(horz)
|
||||
|
||||
|
||||
|
||||
#print("prefs['custom_cols'] %s"%prefs['custom_cols'])
|
||||
|
||||
|
||||
class StandardColumnsTab(QWidget):
|
||||
|
||||
def __init__(self, parent_dialog, plugin_action):
|
||||
self.parent_dialog = parent_dialog
|
||||
self.plugin_action = plugin_action
|
||||
QWidget.__init__(self)
|
||||
|
||||
columns=OrderedDict()
|
||||
|
||||
columns["title"]="Title"
|
||||
columns["authors"]="Author(s)"
|
||||
columns["publisher"]="Publisher"
|
||||
columns["tags"]="Tags"
|
||||
columns["languages"]="Languages"
|
||||
columns["pubdate"]="Published Date"
|
||||
columns["timestamp"]="Date"
|
||||
columns["comments"]="Comments"
|
||||
columns["series"]="Series"
|
||||
columns["identifiers"]="Ids(url id only)"
|
||||
|
||||
self.l = QVBoxLayout()
|
||||
self.setLayout(self.l)
|
||||
|
||||
label = QLabel("The standard calibre metadata columns are listed below. You may choose whether FFDL will fill each column automatically on updates or only for new books.")
|
||||
label.setWordWrap(True)
|
||||
self.l.addWidget(label)
|
||||
self.l.addSpacing(5)
|
||||
|
||||
self.stdcol_newonlycheck = {}
|
||||
|
||||
for key, column in columns.iteritems():
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel(column)
|
||||
#label.setToolTip("Update this %s column(%s) with..."%(key,column['datatype']))
|
||||
horz.addWidget(label)
|
||||
|
||||
newonlycheck = QCheckBox("New Only",self)
|
||||
newonlycheck.setToolTip("Write to %s only for new\nbooks, not updates to existing books."%column)
|
||||
self.stdcol_newonlycheck[key] = newonlycheck
|
||||
if key in prefs['std_cols_newonly']:
|
||||
newonlycheck.setChecked(prefs['std_cols_newonly'][key])
|
||||
horz.addWidget(newonlycheck)
|
||||
|
||||
self.l.addLayout(horz)
|
||||
|
||||
self.l.insertStretch(-1)
|
||||
|
||||
+14
-15
@@ -106,26 +106,21 @@ class AddNewDialog(SizePersistedDialog):
|
||||
|
||||
horz = QHBoxLayout()
|
||||
label = QLabel('If Story Already Exists?')
|
||||
label.setToolTip("What to do if there's already an existing story with the same title and author.")
|
||||
horz.addWidget(label)
|
||||
self.collision = QComboBox(self)
|
||||
self.collision.setToolTip("What to do if there's already an existing story with the same URL or title and author.")
|
||||
# add collision options
|
||||
self.set_collisions()
|
||||
i = self.collision.findText(prefs['collision'])
|
||||
if i > -1:
|
||||
self.collision.setCurrentIndex(i)
|
||||
# self.collision.setToolTip(OVERWRITE+' will replace the existing story.\n'+
|
||||
# UPDATE+' will download new chapters only and add to existing EPUB.\n'+
|
||||
# ADDNEW+' will create a new story with the same title and author.\n'+
|
||||
# SKIP+' will not download existing stories.\n'+
|
||||
# CALIBREONLY+' will not download stories, but will update Calibre metadata.')
|
||||
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 story in Calibre from web site?')
|
||||
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)
|
||||
|
||||
@@ -177,15 +172,17 @@ class FakeLineEdit():
|
||||
def text(self):
|
||||
pass
|
||||
|
||||
class CollectURLDialog(QDialog):
|
||||
class CollectURLDialog(SizePersistedDialog):
|
||||
'''
|
||||
Collect single url for get urls.
|
||||
'''
|
||||
def __init__(self, gui, title):
|
||||
QDialog.__init__(self, gui)
|
||||
def __init__(self, gui, title, url_text):
|
||||
SizePersistedDialog.__init__(self, gui, 'FanFictionDownLoader plugin:get story urls')
|
||||
self.gui = gui
|
||||
self.status=False
|
||||
|
||||
self.setMinimumWidth(300)
|
||||
|
||||
self.l = QGridLayout()
|
||||
self.setLayout(self.l)
|
||||
|
||||
@@ -194,6 +191,7 @@ class CollectURLDialog(QDialog):
|
||||
|
||||
self.l.addWidget(QLabel("URL:"),1,0)
|
||||
self.url = QLineEdit(self)
|
||||
self.url.setText(url_text)
|
||||
self.l.addWidget(self.url,1,1)
|
||||
|
||||
self.ok_button = QPushButton('OK', self)
|
||||
@@ -204,15 +202,16 @@ class CollectURLDialog(QDialog):
|
||||
self.cancel_button.clicked.connect(self.cancel)
|
||||
self.l.addWidget(self.cancel_button,2,1)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
# restore saved size.
|
||||
self.resize_dialog()
|
||||
|
||||
def ok(self):
|
||||
self.status=True
|
||||
self.hide()
|
||||
self.accept()
|
||||
|
||||
def cancel(self):
|
||||
self.status=False
|
||||
self.hide()
|
||||
self.reject()
|
||||
|
||||
class UserPassDialog(QDialog):
|
||||
'''
|
||||
@@ -431,9 +430,9 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
options_layout.addWidget(self.fileform)
|
||||
|
||||
label = QLabel('Update Mode:')
|
||||
label.setToolTip("What sort of update to perform. May set default from plugin configuration.")
|
||||
options_layout.addWidget(label)
|
||||
self.collision = QComboBox(self)
|
||||
self.collision.setToolTip("What sort of update to perform. May set default from plugin configuration.")
|
||||
# add collision options
|
||||
self.set_collisions()
|
||||
i = self.collision.findText(prefs['collision'])
|
||||
@@ -444,7 +443,7 @@ class UpdateExistingDialog(SizePersistedDialog):
|
||||
options_layout.addWidget(self.collision)
|
||||
|
||||
self.updatemeta = QCheckBox('Update Calibre &Metadata?',self)
|
||||
self.updatemeta.setToolTip('Update metadata for story in Calibre from web site? May set default from plugin configuration.')
|
||||
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'])
|
||||
options_layout.addWidget(self.updatemeta)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Jim Miller'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import time, os, copy, threading, re
|
||||
import time, os, copy, threading, re, platform
|
||||
from ConfigParser import SafeConfigParser
|
||||
from StringIO import StringIO
|
||||
from functools import partial
|
||||
@@ -116,9 +116,8 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
# items to prevent GC removing it.
|
||||
self.menu_actions = []
|
||||
self.qaction.setMenu(self.menu)
|
||||
self.menu.aboutToShow.connect(self.about_to_show_menu)
|
||||
|
||||
self.menus_lock = threading.RLock()
|
||||
self.menu.aboutToShow.connect(self.about_to_show_menu)
|
||||
|
||||
def initialization_complete(self):
|
||||
# otherwise configured hot keys won't work until the menu's
|
||||
@@ -134,10 +133,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
|
||||
def rebuild_menus(self):
|
||||
with self.menus_lock:
|
||||
# Show the config dialog
|
||||
# The config dialog can also be shown from within
|
||||
# Preferences->Plugins, which is why the do_user_config
|
||||
# method is defined on the base plugin class
|
||||
do_user_config = self.interface_action_base_plugin.do_user_config
|
||||
self.menu.clear()
|
||||
self.actions_unique_map = {}
|
||||
@@ -148,8 +143,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
triggered=self.add_dialog )
|
||||
|
||||
self.update_action = self.create_menu_item_ex(self.menu, '&Update Existing FanFiction Book(s)', image='plusplus.png',
|
||||
unique_name='Update Existing FanFiction Book(s)',
|
||||
shortcut_name='Update Existing FanFiction Book(s)',
|
||||
triggered=self.update_existing)
|
||||
|
||||
if 'Reading List' in self.gui.iactions and (prefs['addtolists'] or prefs['addtoreadlists']) :
|
||||
@@ -169,48 +162,34 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
|
||||
if addmenutxt:
|
||||
self.add_send_action = self.create_menu_item_ex(self.menu, addmenutxt, image='plusplus.png',
|
||||
unique_name=addmenutxt,
|
||||
shortcut_name=addmenutxt,
|
||||
triggered=partial(self.update_lists,add=True))
|
||||
|
||||
if rmmenutxt:
|
||||
self.add_remove_action = self.create_menu_item_ex(self.menu, rmmenutxt, image='minusminus.png',
|
||||
unique_name=rmmenutxt,
|
||||
shortcut_name=rmmenutxt,
|
||||
triggered=partial(self.update_lists,add=False))
|
||||
|
||||
# try:
|
||||
# self.add_send_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 )
|
||||
# except:
|
||||
# pass
|
||||
# try:
|
||||
# self.add_remove_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 )
|
||||
# except:
|
||||
# pass
|
||||
|
||||
self.menu.addSeparator()
|
||||
self.get_list_action = self.create_menu_item_ex(self.menu, 'Get URLs from Selected Books', image='bookmarks.png',
|
||||
unique_name='Get URLs from Selected Books',
|
||||
shortcut_name='Get URLs from Selected Books',
|
||||
triggered=self.get_list_urls)
|
||||
|
||||
self.get_list_url_action = self.create_menu_item_ex(self.menu, 'Get Story URLs from Web Page', image='view.png',
|
||||
unique_name='Get Story URLs from Web Page',
|
||||
shortcut_name='Get Story URLs from Web Page',
|
||||
triggered=self.get_urls_from_page)
|
||||
|
||||
self.menu.addSeparator()
|
||||
self.config_action = create_menu_action_unique(self, self.menu, '&Configure Plugin', shortcut=False,
|
||||
image= 'config.png',
|
||||
unique_name='Configure FanFictionDownLoader',
|
||||
shortcut_name='Configure FanFictionDownLoader',
|
||||
triggered=partial(do_user_config,parent=self.gui))
|
||||
# print("platform.system():%s"%platform.system())
|
||||
# print("platform.mac_ver()[0]:%s"%platform.mac_ver()[0])
|
||||
if not platform.mac_ver()[0]: # Some macs crash on these menu items for unknown reasons.
|
||||
self.menu.addSeparator()
|
||||
self.config_action = self.create_menu_item_ex(self.menu, '&Configure Plugin',
|
||||
image= 'config.png',
|
||||
unique_name='Configure FanFictionDownLoader',
|
||||
shortcut_name='Configure FanFictionDownLoader',
|
||||
triggered=partial(do_user_config,parent=self.gui))
|
||||
|
||||
self.about_action = create_menu_action_unique(self, self.menu, '&About Plugin', shortcut=False,
|
||||
image= 'images/icon.png',
|
||||
unique_name='About FanFictionDownLoader',
|
||||
shortcut_name='About FanFictionDownLoader',
|
||||
triggered=self.about)
|
||||
self.about_action = self.create_menu_item_ex(self.menu, 'About Plugin',
|
||||
image= 'images/icon.png',
|
||||
unique_name='About FanFictionDownLoader',
|
||||
shortcut_name='About FanFictionDownLoader',
|
||||
triggered=self.about)
|
||||
|
||||
# Before we finalize, make sure we delete any actions for menus that are no longer displayed
|
||||
for menu_id, unique_name in self.old_actions_unique_map.iteritems():
|
||||
@@ -236,10 +215,12 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
def create_menu_item_ex(self, parent_menu, menu_text, image=None, tooltip=None,
|
||||
shortcut=None, triggered=None, is_checked=None, shortcut_name=None,
|
||||
unique_name=None):
|
||||
#print("create_menu_item_ex before %s"%menu_text)
|
||||
ac = create_menu_action_unique(self, parent_menu, menu_text, image, tooltip,
|
||||
shortcut, triggered, is_checked, shortcut_name, unique_name)
|
||||
self.actions_unique_map[ac.calibre_shortcut_unique_name] = ac.calibre_shortcut_unique_name
|
||||
self.menu_actions.append(ac)
|
||||
#print("create_menu_item_ex after %s"%menu_text)
|
||||
return ac
|
||||
|
||||
def plugin_button(self):
|
||||
@@ -254,13 +235,23 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
self._update_reading_lists(self.gui.library_view.get_selected_ids(),add)
|
||||
|
||||
def get_urls_from_page(self):
|
||||
d = CollectURLDialog(self.gui,"Get Story URLs from Web Page")
|
||||
|
||||
if prefs['urlsfromclip']:
|
||||
try:
|
||||
urltxt = self.get_urls_clip(storyurls=False)[0]
|
||||
except:
|
||||
urltxt = ""
|
||||
|
||||
d = CollectURLDialog(self.gui,"Get Story URLs from Web Page",urltxt)
|
||||
d.exec_()
|
||||
if not d.status:
|
||||
return
|
||||
print("URL:%s"%d.url.text())
|
||||
print("get_urls_from_page URL:%s"%d.url.text())
|
||||
|
||||
url_list = get_urls_from_page("%s"%d.url.text())
|
||||
ffdlconfig = SafeConfigParser()
|
||||
ffdlconfig.readfp(StringIO(get_resources("plugin-defaults.ini")))
|
||||
ffdlconfig.readfp(StringIO(prefs['personal.ini']))
|
||||
url_list = get_urls_from_page("%s"%d.url.text(),ffdlconfig)
|
||||
|
||||
if url_list:
|
||||
d = ViewLog(_("List of URLs"),"\n".join(url_list),parent=self.gui)
|
||||
@@ -377,13 +368,14 @@ class FanFictionDownLoaderPlugin(InterfaceAction):
|
||||
options['version'] = self.version
|
||||
print(self.version)
|
||||
self.start_downloads( options, update_books )
|
||||
|
||||
def get_urls_clip(self):
|
||||
|
||||
def get_urls_clip(self,storyurls=True):
|
||||
url_list = []
|
||||
if prefs['urlsfromclip']:
|
||||
for url in unicode(QApplication.instance().clipboard().text()).split():
|
||||
if( self._is_good_downloader_url(url) ):
|
||||
if not storyurls or self._is_good_downloader_url(url):
|
||||
url_list.append(url)
|
||||
|
||||
return url_list
|
||||
|
||||
def apply_settings(self):
|
||||
@@ -483,9 +475,9 @@ make_firstimage_cover:true
|
||||
book['title'] = story.getMetadata("title", removeallentities=True)
|
||||
book['author_sort'] = book['author'] = story.getList("author", removeallentities=True)
|
||||
book['publisher'] = story.getMetadata("site")
|
||||
book['tags'] = writer.getTags() # getTags could be moved up into adapter now. Adapter didn't used to know the fileform
|
||||
book['tags'] = writer.getTags(removeallentities=True) # getTags could be moved up into adapter now. Adapter didn't used to know the fileform
|
||||
book['comments'] = stripHTML(story.getMetadata("description")) #, removeallentities=True) comments handles entities better.
|
||||
book['series'] = story.getMetadata("series")
|
||||
book['series'] = story.getMetadata("series", removeallentities=True)
|
||||
|
||||
# adapter.opener is the element with a threadlock. But del
|
||||
# adapter.opener doesn't work--subproc fails when it tries
|
||||
@@ -551,7 +543,7 @@ make_firstimage_cover:true
|
||||
raise NotGoingToDownload("Skipping duplicate story.","list_remove.png")
|
||||
|
||||
if len(identicalbooks) > 1:
|
||||
raise NotGoingToDownload("More than one identical book--can't tell which to update/overwrite.","minusminus.png")
|
||||
raise NotGoingToDownload("More than one identical book by Identifer URL or title/author(s)--can't tell which book to update/overwrite.","minusminus.png")
|
||||
|
||||
## changed: add new book when CALIBREONLY if none found.
|
||||
if collision == CALIBREONLY and not identicalbooks:
|
||||
@@ -708,7 +700,7 @@ make_firstimage_cover:true
|
||||
self._add_or_update_book(book,options,prefs,mi)
|
||||
|
||||
if options['collision'] == CALIBREONLY or \
|
||||
(options['updatemeta'] and book['good']):
|
||||
( (options['updatemeta'] or book['added']) and book['good'] ):
|
||||
self._update_metadata(db, book['calibre_id'], book, mi, options)
|
||||
|
||||
def _update_bad_book(self,book,db=None,label='errorcol',
|
||||
@@ -770,22 +762,6 @@ make_firstimage_cover:true
|
||||
|
||||
self.previous = self.gui.library_view.currentIndex()
|
||||
db = self.gui.current_db
|
||||
# if display_story_list(self.gui,
|
||||
# 'Downloads finished, confirm to update Calibre',
|
||||
# prefs,
|
||||
# self.qaction.icon(),
|
||||
# job.result,
|
||||
# label_text='Stories will not be added or updated in Calibre without confirmation.',
|
||||
# offer_skip=True):
|
||||
|
||||
# payload = (job.statistics_cols_map, book_statistics_map)
|
||||
# all_ids = set(book_statistics_map.keys())
|
||||
# msg = '<p>Count Pages plugin found <b>%d statistics(s)</b>. ' % len(all_ids) + \
|
||||
# 'Proceed with updating columns in your library?'
|
||||
# self.gui.proceed_question(self._update_database_columns,
|
||||
# payload, job.details,
|
||||
# 'Count log', 'Count complete', msg,
|
||||
# show_copy_button=False)
|
||||
|
||||
book_list = job.result
|
||||
good_list = filter(lambda x : x['good'], book_list)
|
||||
@@ -798,20 +774,20 @@ make_firstimage_cover:true
|
||||
<p>Proceed with updating your library?</p>
|
||||
'''%(len(good_list),len(bad_list))
|
||||
|
||||
htmllog='<html><body><table border="1"><tr><th>Status</th><th>Title</th><th>Author</th><th>URL</th><th>Comment</th></tr>'
|
||||
htmllog='<html><body><table border="1"><tr><th>Status</th><th>Title</th><th>Author</th><th>Comment</th><th>URL</th></tr>'
|
||||
for book in good_list:
|
||||
if 'status' in book:
|
||||
status = book['status']
|
||||
else:
|
||||
status = 'Good'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['url'],book['comment']]) + '</td></tr>'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['comment'],book['url']]) + '</td></tr>'
|
||||
|
||||
for book in bad_list:
|
||||
if 'status' in book:
|
||||
status = book['status']
|
||||
else:
|
||||
status = 'Bad'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['url'],book['comment']]) + '</td></tr>'
|
||||
htmllog = htmllog + '<tr><td>' + '</td><td>'.join([status,book['title'],", ".join(book['author']),book['comment'],book['url']]) + '</td></tr>'
|
||||
|
||||
htmllog = htmllog + '</table></body></html>'
|
||||
|
||||
@@ -890,6 +866,7 @@ make_firstimage_cover:true
|
||||
return book_id
|
||||
|
||||
def _update_metadata(self, db, book_id, book, mi, options):
|
||||
oldmi = db.get_metadata(book_id,index_is_id=True)
|
||||
if prefs['keeptags']:
|
||||
old_tags = db.get_tags(book_id)
|
||||
# remove old Completed/In-Progress only if there's a new one.
|
||||
@@ -905,7 +882,6 @@ make_firstimage_cover:true
|
||||
mi.languages=[book['all_metadata']['langcode']]
|
||||
else:
|
||||
# Set language english, but only if not already set.
|
||||
oldmi = db.get_metadata(book_id,index_is_id=True)
|
||||
if not oldmi.languages:
|
||||
mi.languages=['eng']
|
||||
|
||||
@@ -922,6 +898,20 @@ make_firstimage_cover:true
|
||||
autid=db.get_author_id(auth)
|
||||
db.set_link_field_for_author(autid, unicode(authurls[i]),
|
||||
commit=False, notify=False)
|
||||
|
||||
# implement 'newonly' flags here by setting to the current
|
||||
# value again.
|
||||
if not book['added']:
|
||||
for (col,newonly) in prefs['std_cols_newonly'].iteritems():
|
||||
if newonly:
|
||||
if col == "identifiers":
|
||||
mi.set_identifiers(oldmi.get_identifiers())
|
||||
else:
|
||||
try:
|
||||
mi.__setattr__(col,oldmi.__getattribute__(col))
|
||||
except AttributeError:
|
||||
print("AttributeError? %s"%col)
|
||||
pass
|
||||
|
||||
db.set_metadata(book_id,mi)
|
||||
|
||||
@@ -936,6 +926,9 @@ make_firstimage_cover:true
|
||||
print("%s not an existing column, skipping."%col)
|
||||
continue
|
||||
coldef = custom_columns[col]
|
||||
if col in prefs['custom_cols_newonly'] and prefs['custom_cols_newonly'][col] and not book['added']:
|
||||
print("Skipping custom column(%s) update, set to New Books Only"%coldef['name'])
|
||||
continue
|
||||
if not meta.startswith('status-') and meta not in book['all_metadata'] or \
|
||||
meta.startswith('status-') and 'status' not in book['all_metadata']:
|
||||
print("No value for %s, skipping custom column(%s) update."%(meta,coldef['name']))
|
||||
|
||||
@@ -109,6 +109,9 @@ def do_download_for_worker(book,options):
|
||||
when run as a worker job
|
||||
'''
|
||||
try:
|
||||
# import logging
|
||||
# logging.basicConfig(level=logging.DEBUG,format="%(levelname)s:%(filename)s(%(lineno)d):%(message)s")
|
||||
|
||||
book['comment'] = 'Download started...'
|
||||
|
||||
ffdlconfig = SafeConfigParser()
|
||||
@@ -154,11 +157,13 @@ def do_download_for_worker(book,options):
|
||||
# update now handled by pre-populating the old images and
|
||||
# chapters in the adapter rather than merging epubs.
|
||||
urlchaptercount = int(story.getMetadata('numChapters'))
|
||||
(url,chaptercount,
|
||||
(url,
|
||||
chaptercount,
|
||||
adapter.oldchapters,
|
||||
adapter.oldimgs,
|
||||
adapter.oldcover,
|
||||
adapter.calibrebookmark) = get_update_data(book['epub_for_update'])
|
||||
adapter.calibrebookmark,
|
||||
adapter.logfile) = get_update_data(book['epub_for_update'])
|
||||
|
||||
print("Do update - epub(%d) vs url(%d)" % (chaptercount, urlchaptercount))
|
||||
print("write to %s"%outfile)
|
||||
|
||||
+235
-5
@@ -38,6 +38,7 @@ category_label:Category
|
||||
genre_label:Genre
|
||||
language_label:Language
|
||||
characters_label:Characters
|
||||
ships_label:Relationships
|
||||
series_label:Series
|
||||
## Completed/In-Progress
|
||||
status_label:Status
|
||||
@@ -70,7 +71,7 @@ version_label:FFDL Version
|
||||
## 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,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
titlepage_entries: series,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
|
||||
@@ -121,11 +122,20 @@ allow_unsafe_filename: false
|
||||
|
||||
## entries to make epub subjects and calibre tags
|
||||
## lastupdate creates two tags: "Last Update Year/Month: %Y/%m" and "Last Update: %Y/%m/%d"
|
||||
include_subject_tags: extratags, genre, category, characters, lastupdate, status
|
||||
include_subject_tags: extratags, genre, category, characters, ships, lastupdate, status
|
||||
|
||||
## extra tags (comma separated) to include, primarily for epub.
|
||||
extratags: FanFiction
|
||||
|
||||
## extra categories, genres, characters, ships and warnings can be
|
||||
## configured. Used primarily for sites that are dedicated to a genre
|
||||
## or 'ship and so don't included it for every story.
|
||||
#extracategories:
|
||||
#extragenres:
|
||||
#extracharacters:
|
||||
#extraships:
|
||||
#extrawarnings:
|
||||
|
||||
## number of seconds to sleep between calls to the story site. May by
|
||||
## useful if pulling large numbers of stories or if the site is slow.
|
||||
#slow_down_sleep_time:0.5
|
||||
@@ -197,9 +207,24 @@ windows_eol: true
|
||||
zip_output: false
|
||||
|
||||
## epub carries the TOC in metadata.
|
||||
## mobi generated from epub will have a TOC at the end.
|
||||
## mobi generated from epub by calibre will have a TOC at the end.
|
||||
include_tocpage: false
|
||||
|
||||
## include a Update Log page before the story text. If included, the
|
||||
## log will be updated each time the epub is an all the metadata
|
||||
## fields that have changed since the last update (typically
|
||||
## dateUpdated,numChapters,numWords at a minimum) will be shown.
|
||||
## Great for tracking when chapters came out and when the description,
|
||||
## etc changed.
|
||||
include_logpage: false
|
||||
|
||||
## items to include in the log page Empty metadata entries, or those
|
||||
## that haven't changed since the last update, will *not* appear, even
|
||||
## 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
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
@@ -214,10 +239,12 @@ background_color: ffffff
|
||||
## Allow customization of CSS. Make sure to keep at least one space
|
||||
## at the start of each line and to escape % to %%. Also need
|
||||
## background_color to be in the same section, if included in CSS.
|
||||
## 'adobe-text-layout: optimizeSpeed;' prevents hyphenation on newer Nooks
|
||||
output_css:
|
||||
body { background-color: #%(background_color)s;
|
||||
text-align: justify;
|
||||
margin: 2%%; }
|
||||
margin: 2%%;
|
||||
adobe-text-layout: optimizeSpeed; }
|
||||
pre { font-size: x-small; }
|
||||
sml { font-size: small; }
|
||||
h1 { text-align: center; }
|
||||
@@ -301,6 +328,11 @@ nook_img_fix:true
|
||||
## URLs like: http://test1.com?sid=12345
|
||||
[test1.com]
|
||||
extratags: FanFiction,Testing
|
||||
extracategories:Fafner
|
||||
extragenres:Romance,Fluff
|
||||
extracharacters:Reginald Smythe-Smythe,Mokona
|
||||
extraships:Smythe-Smythe/Mokona
|
||||
extrawarnings:Extreme Bogosity
|
||||
|
||||
## If necessary, you can define [<site>:<format>] sections to
|
||||
## customize the formats differently for the same site. Overrides
|
||||
@@ -327,6 +359,11 @@ extratags: FanFiction,Testing,HTML
|
||||
#is_adult:true
|
||||
|
||||
[ashwinder.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape,Hermione Granger
|
||||
extraships:Severus Snape/Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -335,6 +372,9 @@ extratags: FanFiction,Testing,HTML
|
||||
#password:yourpassword
|
||||
|
||||
[castlefans.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Castle
|
||||
|
||||
## 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
|
||||
@@ -358,6 +398,11 @@ extratags: FanFiction,Testing,HTML
|
||||
cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
|
||||
[dark-solace.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Buffy: The Vampire Slayer
|
||||
extracharacters:Buffy, Spike
|
||||
extraships:Spike/Buffy
|
||||
|
||||
## 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
|
||||
@@ -366,6 +411,11 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[dramione.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Hermione Granger
|
||||
extraships:Draco Malfoy/Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -379,6 +429,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[erosnsappho.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -389,6 +442,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
|
||||
[fanfiction.mugglenet.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -402,6 +458,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[fanfiction.portkey.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extraships:Harry Potter/Hermione Granger
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -424,6 +484,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[grangerenchanted.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -437,26 +501,51 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[hlfiction.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Highlander
|
||||
|
||||
[ksarchive.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Star Trek
|
||||
extracharacters:Kirk,Spock
|
||||
extraships:Kirk/Spock
|
||||
|
||||
[lumos.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[national-library.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:West Wing
|
||||
|
||||
[ncisfic.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
[nfacommunity.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[nha.magical-worlds.us]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Buffy: The Vampire Slayer
|
||||
extracharacters:Willow
|
||||
|
||||
[occlumency.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape
|
||||
|
||||
## 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
|
||||
@@ -465,6 +554,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[onedirectionfanfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:One Direction
|
||||
|
||||
## 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
|
||||
@@ -478,18 +570,28 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[ponyfictionarchive.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:My Little Pony: Friendship is Magic
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[pretendercentre.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:The Pretender
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[samdean.archive.nu]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Supernatural
|
||||
extracharacters:Sam,Dean
|
||||
extraships:Sam/Dean
|
||||
|
||||
[sg1-heliopolis.com]
|
||||
## Some sites do not require a login, but do require the user to
|
||||
@@ -498,6 +600,8 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[stargate-atlantis.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: Atlantis
|
||||
|
||||
[svufiction.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -513,6 +617,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[thehexfiles.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Harry Potter
|
||||
extraships:Harry Potter/Draco Malfoy
|
||||
|
||||
[themasque.net]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -528,6 +636,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[thequidditchpitch.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -541,14 +652,40 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[www.adastrafanfic.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Star Trek
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.dracoandginny.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Ginny Weasley
|
||||
extraships:Draco Malfoy/Ginny Weasley
|
||||
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.thealphagate.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: SG-1
|
||||
|
||||
[www.checkmated.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -563,6 +700,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[www.dokuga.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:InuYasha
|
||||
extracharacters:Sesshoumaru,Kagome
|
||||
extraships:Sesshoumaru/Kagome
|
||||
|
||||
[www.fanfiction.net]
|
||||
## fanfiction.net's 'cover' images are really just tiny thumbnails.
|
||||
@@ -580,6 +721,9 @@ never_make_cover: true
|
||||
[www.ficbook.net]
|
||||
|
||||
[www.fictionalley.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -601,6 +745,9 @@ extratags:
|
||||
#password:yourpassword
|
||||
|
||||
[www.fimfiction.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:My Little Pony: Friendship is Magic
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -612,20 +759,33 @@ extratags:
|
||||
#fail_on_password: false
|
||||
|
||||
[www.harrypotterfanfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.hpfandom.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.hpfanficarchive.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
[www.ik-eternal.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:InuYasha
|
||||
extracharacters:InuYasha,Kagome
|
||||
extraships:InuYasha/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
|
||||
@@ -639,6 +799,9 @@ extratags:
|
||||
#is_adult:true
|
||||
|
||||
[www.libraryofmoria.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Lord of the Rings
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -647,6 +810,9 @@ extratags:
|
||||
[www.mediaminer.org]
|
||||
|
||||
[www.midnightwhispers.ca]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Queer as Folk
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -657,8 +823,14 @@ extratags:
|
||||
cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
|
||||
|
||||
[www.ncisfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
[www.phoenixsong.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extraships:Harry Potter/Ginny Weasley
|
||||
|
||||
## 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
|
||||
@@ -667,16 +839,43 @@ cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
|
||||
#password:yourpassword
|
||||
|
||||
[www.potionsandsnitches.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
[www.prisonbreakfic.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Prison Break
|
||||
|
||||
[www.scarvesandcoffee.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Glee
|
||||
extracharacters:Kurt Hummel,Blaine Anderson
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.sinful-desire.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Supernatural
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.siye.co.uk]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Harry Potter,Ginny Weasley
|
||||
extraships:Harry Potter/Ginny Weasley
|
||||
|
||||
[www.squidge.org/peja]
|
||||
# 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,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
titlepage_entries: series,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.
|
||||
@@ -684,6 +883,16 @@ titlepage_entries: series,category,genre,language,characters,status,datePublishe
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
|
||||
[www.storiesofarda.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Lord of the Rings
|
||||
|
||||
[www.thepetulantpoetess.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
[www.thewriterscoffeeshop.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -715,8 +924,13 @@ collect_series: false
|
||||
#password:yourpassword
|
||||
|
||||
[www.twilightarchives.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
[www.twilighted.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
## 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
|
||||
@@ -728,6 +942,9 @@ collect_series: false
|
||||
collect_series: false
|
||||
|
||||
[www.twiwrite.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
## 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
|
||||
@@ -739,6 +956,11 @@ collect_series: false
|
||||
collect_series: false
|
||||
|
||||
[www.walkingtheplank.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape,Harry Potter
|
||||
extraships:Severus Snape/Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -759,7 +981,15 @@ collect_series: false
|
||||
## personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.wolverineandrogue.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:X-Men Movie
|
||||
extracharacters:Wolverine,Rogue
|
||||
|
||||
[www.wraithbait.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: Atlantis
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
|
||||
+5
-3
@@ -128,7 +128,7 @@ def main():
|
||||
config.set("overrides",var,val)
|
||||
|
||||
if options.list:
|
||||
retlist = get_urls_from_page(args[0])
|
||||
retlist = get_urls_from_page(args[0], config)
|
||||
print "\n".join(retlist)
|
||||
|
||||
return
|
||||
@@ -193,11 +193,13 @@ def main():
|
||||
# update now handled by pre-populating the old
|
||||
# images and chapters in the adapter rather than
|
||||
# merging epubs.
|
||||
(url,chaptercount,
|
||||
(url,
|
||||
chaptercount,
|
||||
adapter.oldchapters,
|
||||
adapter.oldimgs,
|
||||
adapter.oldcover,
|
||||
adapter.calibrebookmark) = get_update_data(args[0])
|
||||
adapter.calibrebookmark,
|
||||
adapter.logfile) = get_update_data(args[0])
|
||||
|
||||
writeStory(config,adapter,"epub")
|
||||
|
||||
|
||||
@@ -92,6 +92,11 @@ import adapter_wizardtalesnet
|
||||
import adapter_nhamagicalworldsus
|
||||
import adapter_hlfictionnet
|
||||
import adapter_grangerenchantedcom
|
||||
import adapter_dracoandginnycom
|
||||
import adapter_scarvesandcoffeenet
|
||||
import adapter_thepetulantpoetesscom
|
||||
import adapter_wolverineandroguecom
|
||||
import adapter_sinfuldesireorg
|
||||
|
||||
## This bit of complexity allows adapters to be added by just adding
|
||||
## importing. It eliminates the long if/else clauses we used to need
|
||||
@@ -112,7 +117,7 @@ for x in imports():
|
||||
|
||||
def getAdapter(config,url,fileform=None):
|
||||
## fix up leading protocol.
|
||||
fixedurl = re.sub(r"(?i)^[htp]+[:/]+","http://",url.strip())
|
||||
fixedurl = re.sub(r"(?i)^[htps]+[:/]+","http://",url.strip())
|
||||
if not fixedurl.startswith("http"):
|
||||
fixedurl = "http://%s"%url
|
||||
## remove any trailing '#' locations.
|
||||
@@ -127,7 +132,7 @@ def getAdapter(config,url,fileform=None):
|
||||
if( domain != parsedUrl.netloc ):
|
||||
fixedurl = fixedurl.replace(parsedUrl.netloc,domain)
|
||||
|
||||
logging.debug("site:"+domain)
|
||||
logging.debug("trying url:"+url)
|
||||
cls = getClassFor(domain)
|
||||
if not cls and domain.startswith("www."):
|
||||
domain = domain.replace("www.","")
|
||||
|
||||
@@ -37,7 +37,6 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Star Trek")
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
|
||||
@@ -50,8 +50,17 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/works/'+self.story.getMetadata('storyId'))
|
||||
# get storyId from url--url validation guarantees query correct
|
||||
m = re.match(self.getSiteURLPattern(),url)
|
||||
if m:
|
||||
self.story.setMetadata('storyId',m.group('id'))
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/works/'+self.story.getMetadata('storyId'))
|
||||
else:
|
||||
raise exceptions.InvalidStoryURL(url,
|
||||
self.getSiteDomain(),
|
||||
self.getSiteExampleURLs())
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ao3')
|
||||
@@ -70,10 +79,11 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
return ['www.archiveofourown.org','archiveofourown.org']
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/works/123456"
|
||||
return "http://"+self.getSiteDomain()+"/works/123456 http://"+self.getSiteDomain()+"/collections/Some_Archive/works/123456"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://")+"(www\.)?"+re.escape(self.getSiteDomain()+"/works/")+r"\d+(/chapters/\d+)?/?$"
|
||||
# http://archiveofourown.org/collections/Smallville_Slash_Archive/works/159770
|
||||
return re.escape("http://")+"(www.)?"+re.escape(self.getSiteDomain())+r"(/collections/[^/]+)?/works/(?P<id>\d+)(/chapters/\d+)?/?$"
|
||||
|
||||
## Login
|
||||
def needToLoginCheck(self, data):
|
||||
@@ -221,9 +231,9 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
|
||||
self.story.addToList('characters',char.string)
|
||||
a = metasoup.find('dd',{'class':"relationship tags"})
|
||||
if a != None:
|
||||
chars = a.findAll('a',{'class':"tag"})
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
ships = a.findAll('a',{'class':"tag"})
|
||||
for ship in ships:
|
||||
self.story.addToList('ships',ship.string)
|
||||
|
||||
|
||||
stats = metasoup.find('dl',{'class':'stats'})
|
||||
|
||||
@@ -56,11 +56,6 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','asph')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@@ -80,11 +80,6 @@ class CastleFansOrgAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','cslf') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Castle") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%b %d, %Y" # XXX
|
||||
|
||||
@@ -56,11 +56,6 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','csph')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
#self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@@ -44,25 +44,20 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
self.username = "" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
|
||||
self._setURL('http://' + self.getSiteDomain() + '/story.php?story='+self.story.getMetadata('storyId'))
|
||||
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','chm')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%b %d, %Y"
|
||||
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
@@ -82,7 +77,7 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def performLogin(self, url):
|
||||
params = {}
|
||||
|
||||
@@ -93,8 +88,8 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
params['name'] = self.getConfig("username")
|
||||
params['pass'] = self.getConfig("password")
|
||||
params['login'] = 'yes'
|
||||
params['submit'] = 'login'
|
||||
|
||||
params['submit'] = 'login'
|
||||
|
||||
loginUrl = 'http://' + self.getSiteDomain()+'/login.php'
|
||||
d = self._fetchUrl(loginUrl,params)
|
||||
e = self._fetchUrl(url)
|
||||
@@ -135,33 +130,33 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
# The actual text that is used to announce you need to be an
|
||||
# adult varies from site to site. Again, print data before
|
||||
# the title search to troubleshoot.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Title
|
||||
a = soup.findAll('span', {'class' : 'storytitle'})
|
||||
self.story.setMetadata('title',a[0].string)
|
||||
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = a[1].find('a', href=re.compile(r"authors.php\?name\=\w+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
|
||||
a = soup.find('select', {'name' : 'chapter'})
|
||||
if a == None:
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
for chapter in a.findAll('option'):
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/story.php?story='+self.story.getMetadata('storyId')+'&chapter='+chapter['value']))
|
||||
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# eFiction sites don't help us out a lot with their meta data
|
||||
@@ -174,7 +169,7 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
# website does not keep track of word count, and there is no convenient way to calculate it
|
||||
|
||||
summary = soup.find('fieldset')
|
||||
@@ -182,21 +177,23 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
summary.name='div'
|
||||
self.setDescription(url,summary)
|
||||
|
||||
|
||||
|
||||
# <span class="label">Rated:</span> NC-17<br /> etc
|
||||
table = soup.findAll('div', {'class' : 'text'})[1]
|
||||
for labels in table.findAll('tr'):
|
||||
value = labels.findAll('td')[1]
|
||||
label = labels.findAll('td')[0]
|
||||
|
||||
|
||||
|
||||
if 'Rating' in stripHTML(label):
|
||||
self.story.setMetadata('rating', stripHTML(value))
|
||||
|
||||
if 'Ship' in stripHTML(label):
|
||||
if value.string != "none/none":
|
||||
self.story.addToList('ships',value.string)
|
||||
for char in value.string.split('/'):
|
||||
if char != 'none':
|
||||
self.story.addToList('characters',char)
|
||||
self.story.addToList('characters',char)
|
||||
|
||||
if 'Status' in stripHTML(label):
|
||||
if value.find('img', {'src' : 'img/incomplete.gif'}) == None:
|
||||
@@ -206,11 +203,11 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
|
||||
if 'Published' in stripHTML(label):
|
||||
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
|
||||
if 'Updated' in stripHTML(label):
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
a = self._fetchUrl(self.story.getMetadata('authorUrl')+'&cat=stories')
|
||||
|
||||
a = self._fetchUrl(self.story.getMetadata('authorUrl')+'&cat=stories')
|
||||
for story in bs.BeautifulSoup(a).findAll('table', {'class' : 'storyinfo'}):
|
||||
a = story.find('a', href=re.compile(r"review.php\?s\="+self.story.getMetadata('storyId')+'&act=view'))
|
||||
if a != None:
|
||||
@@ -220,8 +217,8 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
if 'genre' in stripHTML(label):
|
||||
for genre in value.findAll('img'):
|
||||
self.story.addToList('genre',genre['title'])
|
||||
|
||||
|
||||
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
@@ -229,11 +226,11 @@ class CheckmatedComAdapter(BaseSiteAdapter):
|
||||
|
||||
soup = bs.BeautifulSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
|
||||
div = soup.find('div', {'id' : 'resizeableText'})
|
||||
div.find('div', {'class' : 'storyTools'}).extract()
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
|
||||
@@ -56,11 +56,6 @@ class DarkSolaceOrgAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','dksl')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Buffy the Vampire Slayer")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d %B %Y"
|
||||
|
||||
@@ -56,11 +56,6 @@ class DokugaComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','dkg')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","InuYasha")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
if 'fanfiction' in self.story.getMetadata('section'):
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Fanficdownloader team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from .. import BeautifulSoup as bs
|
||||
from ..htmlcleanup import stripHTML
|
||||
from .. import exceptions as exceptions
|
||||
|
||||
from base_adapter import BaseSiteAdapter, makeDate
|
||||
|
||||
def getClass():
|
||||
return DracoAndGinnyComAdapter
|
||||
|
||||
# Class name has to be unique. Our convention is camel case the
|
||||
# sitename with Adapter at the end. www is skipped.
|
||||
class DracoAndGinnyComAdapter(BaseSiteAdapter):
|
||||
|
||||
def __init__(self, config, url):
|
||||
BaseSiteAdapter.__init__(self, config, url)
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','dcagn')
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%b %d, %Y"
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
return 'www.dracoandginny.com'
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/viewstory.php?sid=1234"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
|
||||
|
||||
## Login seems to be reasonably standard across eFiction sites.
|
||||
def needToLoginCheck(self, data):
|
||||
if 'Registered Users Only' in data \
|
||||
or 'There is no such account on our website' in data \
|
||||
or "That password doesn't match the one in our database" in data:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def performLogin(self, url):
|
||||
params = {}
|
||||
|
||||
if self.password:
|
||||
params['penname'] = self.username
|
||||
params['password'] = self.password
|
||||
else:
|
||||
params['penname'] = self.getConfig("username")
|
||||
params['password'] = self.getConfig("password")
|
||||
params['cookiecheck'] = '1'
|
||||
params['submit'] = 'Submit'
|
||||
|
||||
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
|
||||
logging.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
|
||||
params['penname']))
|
||||
|
||||
d = self._fetchUrl(loginUrl, params)
|
||||
|
||||
if "Member Account" not in d : #Member Account
|
||||
logging.info("Failed to login to URL %s as %s" % (loginUrl,
|
||||
params['penname']))
|
||||
raise exceptions.FailedToLogin(url,params['penname'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# Weirdly, different sites use different warning numbers.
|
||||
# If the title search below fails, there's a good chance
|
||||
# you need a different number. print data at that point
|
||||
# and see what the 'click here to continue' url says.
|
||||
addurl = "&ageconsent=ok&warning=2"
|
||||
else:
|
||||
addurl=""
|
||||
|
||||
# index=1 makes sure we see the story chapter index. Some
|
||||
# sites skip that for one-chapter stories.
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
if self.needToLoginCheck(data):
|
||||
# need to log in for this one.
|
||||
self.performLogin(url)
|
||||
data = self._fetchUrl(url)
|
||||
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
|
||||
if m != None:
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# We tried the default and still got a warning, so
|
||||
# let's pull the warning number from the 'continue'
|
||||
# link and reload data.
|
||||
addurl = m.group(1)
|
||||
# correct stupid & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL 2nd try: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise exceptions.AdultCheckRequired(self.url)
|
||||
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.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.
|
||||
|
||||
## Title
|
||||
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
|
||||
self.story.setMetadata('title',a.string)
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['href']+addurl))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# eFiction sites don't help us out a lot with their meta data
|
||||
# formating, so it's a little ugly.
|
||||
|
||||
# utility method
|
||||
def defaultGetattr(d,k):
|
||||
try:
|
||||
return d[k]
|
||||
except:
|
||||
return ""
|
||||
|
||||
# <span class="label">Rated:</span> NC-17<br /> etc
|
||||
content=soup.find('div',{'class' : 'listbox'})
|
||||
|
||||
self.setDescription(url,content.find('blockquote'))
|
||||
|
||||
for genre in content.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1')):
|
||||
self.story.addToList('genre',genre.string)
|
||||
|
||||
for warning in content.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2')):
|
||||
self.story.addToList('warnings',warning.string)
|
||||
|
||||
labels = content.findAll('b')
|
||||
|
||||
for labelspan in labels:
|
||||
value = labelspan.nextSibling
|
||||
label = labelspan.string
|
||||
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
svalue = ""
|
||||
while not defaultGetattr(value,'class') == 'label':
|
||||
svalue += str(value)
|
||||
value = value.nextSibling
|
||||
self.setDescription(url,svalue)
|
||||
|
||||
if 'Word count' in label:
|
||||
self.story.setMetadata('numWords', value.split(' |')[0])
|
||||
|
||||
if 'Rating' in label:
|
||||
self.story.setMetadata('rating', value)
|
||||
|
||||
if 'Categories' in label:
|
||||
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
|
||||
for cat in cats:
|
||||
self.story.addToList('category',cat.string)
|
||||
|
||||
if 'Characters' in label:
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Genre' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
|
||||
for genre in genres:
|
||||
self.story.addToList('genre',genre.string)
|
||||
|
||||
if 'Warnings' in label:
|
||||
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
|
||||
for warning in warnings:
|
||||
self.story.addToList('warnings',warning.string)
|
||||
|
||||
if 'Completed' in label:
|
||||
if 'Yes' in value:
|
||||
self.story.setMetadata('status', 'Completed')
|
||||
else:
|
||||
self.story.setMetadata('status', 'In-Progress')
|
||||
|
||||
if 'Published' in label:
|
||||
self.story.setMetadata('datePublished', makeDate(stripHTML(value.nextSibling).split(' |')[0], self.dateformat))
|
||||
|
||||
if 'Updated' in label:
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
try:
|
||||
# Find Series name from series URL.
|
||||
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
|
||||
series_name = a.string
|
||||
series_url = 'http://'+self.host+'/'+a['href']
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
|
||||
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
|
||||
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
|
||||
i=1
|
||||
for a in storyas:
|
||||
# skip 'report this' and 'TOC' links
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
break
|
||||
i+=1
|
||||
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
pass
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
logging.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
div = soup.find('div', {'class' : 'listbox'})
|
||||
|
||||
if None == div:
|
||||
div = soup.find('div', {'id' : 'story'})
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
@@ -55,11 +55,6 @@ class DramioneOrgAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','drmn')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d %B %Y"
|
||||
|
||||
@@ -56,11 +56,6 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','essph')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d/%m/%y"
|
||||
|
||||
@@ -176,7 +176,9 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
genrelist = metalist[0].split('/') # Hurt/Comfort already changed above.
|
||||
goodgenres=True
|
||||
for g in genrelist:
|
||||
if g not in ffnetgenres:
|
||||
print("g:(%s)"%g)
|
||||
if g.strip() not in ffnetgenres:
|
||||
print("g not in ffnetgenres")
|
||||
goodgenres=False
|
||||
if goodgenres:
|
||||
self.story.extendList('genre',genrelist)
|
||||
@@ -239,6 +241,15 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
## additional to what ever the
|
||||
## slow_down_sleep_time setting is.
|
||||
data = self._fetchUrl(url)
|
||||
|
||||
# some ancient stories have body tags inside them that cause
|
||||
# soup parsing to discard the content. For story text we
|
||||
# don't care about anything before "<div class='storytextp"
|
||||
# (there's a space after storytextp, so no close quote(')) and
|
||||
# this kills any body tags.
|
||||
data = data[data.index("<div class='storytextp"):]
|
||||
data.replace("<body","<notbody").replace("<BODY","<NOTBODY")
|
||||
|
||||
soup = bs.BeautifulSoup(data)
|
||||
|
||||
## Remove the 'share' button.
|
||||
|
||||
@@ -37,7 +37,6 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query correct
|
||||
|
||||
@@ -133,11 +133,8 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
|
||||
if storyMetadata["categories"][category]:
|
||||
self.story.addToList("genre", category)
|
||||
|
||||
self.story.addToList("category", "My Little Pony")
|
||||
|
||||
self.story.setMetadata("numWords", str(storyMetadata["words"]))
|
||||
|
||||
|
||||
# fimfic is the first site with an explicit cover image.
|
||||
if self.getConfig('include_images') and "image" in storyMetadata.keys():
|
||||
coverurl = storyMetadata["image"]
|
||||
|
||||
@@ -60,11 +60,6 @@ class GrangerEnchantedCom(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','gech')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d/%b/%Y"
|
||||
|
||||
@@ -37,7 +37,6 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only psid=1234
|
||||
|
||||
@@ -55,11 +55,6 @@ class HLFictionNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','hlf')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Highlander")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%y"
|
||||
|
||||
@@ -60,11 +60,6 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','hpfdm') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%Y.%m.%d" # XXX
|
||||
|
||||
@@ -55,11 +55,6 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','hpffa')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d, %Y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class IkEternalNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ike')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","InuYasha")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d, %Y"
|
||||
|
||||
@@ -60,11 +60,6 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ksa') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Star Trek") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%b/%d/%Y" # XXX
|
||||
@@ -167,7 +162,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
self.story.setMetadata('author',stripHTML(a))
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
@@ -190,7 +185,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
labels = soup.findAll('span',{'class':'label'})
|
||||
for labelspan in labels:
|
||||
value = labelspan.nextSibling
|
||||
label = labelspan.string
|
||||
label = stripHTML(labelspan)
|
||||
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
@@ -215,50 +210,50 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
|
||||
if 'Categories' in label:
|
||||
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
|
||||
catstext = [cat.string for cat in cats]
|
||||
catstext = [stripHTML(cat) for cat in cats]
|
||||
for cat in catstext:
|
||||
# ran across one story with an empty <a href="browse.php?type=categories&catid=1"></a>
|
||||
# tag in the desc once.
|
||||
if cat and cat.strip() in ('Poetry','Essays'):
|
||||
self.story.addToList('category',cat.string)
|
||||
self.story.addToList('category',stripHTML(cat))
|
||||
|
||||
if 'Characters' in label:
|
||||
self.story.addToList('characters','Kirk')
|
||||
self.story.addToList('characters','Spock')
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
charstext = [char.string for char in chars]
|
||||
charstext = [stripHTML(char) for char in chars]
|
||||
for char in charstext:
|
||||
self.story.addToList('characters',char.string)
|
||||
self.story.addToList('characters',stripHTML(char))
|
||||
|
||||
## Not all sites use Genre, but there's no harm to
|
||||
## leaving it in. Check to make sure the type_id number
|
||||
## is correct, though--it's site specific.
|
||||
if 'Genre' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1')) # XXX
|
||||
genrestext = [genre.string for genre in genres]
|
||||
genrestext = [stripHTML(genre) for genre in genres]
|
||||
self.genre = ', '.join(genrestext)
|
||||
for genre in genrestext:
|
||||
self.story.addToList('genre',genre.string)
|
||||
self.story.addToList('genre',stripHTML(genre))
|
||||
|
||||
## In addition to Genre (which is very site specific) KSA
|
||||
## has 'Story Type', which is much more what most sites
|
||||
## call genre.
|
||||
if 'Story Type' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=5')) # XXX
|
||||
genrestext = [genre.string for genre in genres]
|
||||
genrestext = [stripHTML(genre) for genre in genres]
|
||||
self.genre = ', '.join(genrestext)
|
||||
for genre in genrestext:
|
||||
self.story.addToList('genre',genre.string)
|
||||
self.story.addToList('genre',stripHTML(genre))
|
||||
|
||||
## Not all sites use Warnings, but there's no harm to
|
||||
## leaving it in. Check to make sure the type_id number
|
||||
## is correct, though--it's site specific.
|
||||
if 'Warnings' in label:
|
||||
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2')) # XXX
|
||||
warningstext = [warning.string for warning in warnings]
|
||||
warningstext = [stripHTML(warning) for warning in warnings]
|
||||
self.warning = ', '.join(warningstext)
|
||||
for warning in warningstext:
|
||||
self.story.addToList('warnings',warning.string)
|
||||
self.story.addToList('warnings',stripHTML(warning))
|
||||
|
||||
if 'Completed' in label:
|
||||
if 'Yes' in value:
|
||||
@@ -277,7 +272,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
|
||||
try:
|
||||
# Find Series name from series URL.
|
||||
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
|
||||
series_name = a.string
|
||||
series_name = stripHTML(a)
|
||||
series_url = 'http://'+self.host+'/'+a['href']
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
|
||||
@@ -56,11 +56,6 @@ class LibraryOfMoriaComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','lom')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Lord of the Rings")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d, %Y"
|
||||
|
||||
@@ -56,11 +56,6 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','lsph')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@@ -60,11 +60,6 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','mw') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Queer as Folk") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d, %Y" # XXX
|
||||
|
||||
@@ -59,11 +59,6 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','mgln') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%y" # XXX
|
||||
|
||||
@@ -55,11 +55,6 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ntlb')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","West Wing")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m-%d-%y"
|
||||
@@ -154,6 +149,10 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
|
||||
for char in value.string.split(', '):
|
||||
self.story.addToList('characters',char)
|
||||
|
||||
if 'Pairing' in label:
|
||||
for char in value.string.split(', '):
|
||||
self.story.addToList('ships',char)
|
||||
|
||||
if 'Warnings' in label:
|
||||
for warning in value.string.split(', '):
|
||||
self.story.addToList('warnings',warning)
|
||||
|
||||
@@ -55,11 +55,6 @@ class NCISFicComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ncisf')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","NCIS")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m-%d-%y"
|
||||
@@ -154,6 +149,10 @@ class NCISFicComAdapter(BaseSiteAdapter):
|
||||
for char in value.string.split(', '):
|
||||
self.story.addToList('characters',char)
|
||||
|
||||
if 'Pairing' in label:
|
||||
for char in value.string.split(', '):
|
||||
self.story.addToList('ships',char)
|
||||
|
||||
if 'Warnings' in label:
|
||||
for warning in value.string.split(', '):
|
||||
self.story.addToList('warnings',warning)
|
||||
|
||||
@@ -56,11 +56,6 @@ class NCISFictionComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ncisfn')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","NCIS")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d/%m/%Y"
|
||||
@@ -157,6 +152,11 @@ class NCISFictionComAdapter(BaseSiteAdapter):
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Pairing' in label:
|
||||
ships = value.findAll('a')
|
||||
for ship in ships:
|
||||
self.story.addToList('ships',ship.string)
|
||||
|
||||
if 'Genre' in label:
|
||||
genres = value.findAll('a')
|
||||
for genre in genres:
|
||||
|
||||
@@ -60,11 +60,6 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','nfa') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","NCIS") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y" # XXX
|
||||
|
||||
@@ -56,11 +56,6 @@ class NHAMagicalWorldsUsAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','nha')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Buffy the Vampire Slayer")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = " %m/%d/%y"
|
||||
@@ -122,7 +117,7 @@ class NHAMagicalWorldsUsAdapter(BaseSiteAdapter):
|
||||
|
||||
|
||||
# Find the chapters:
|
||||
chapters=soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+'&chapter=\d$'))
|
||||
chapters=soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+'&chapter=\d+$'))
|
||||
if len(chapters) == 0:
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
|
||||
@@ -56,11 +56,6 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','osph')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','odf')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","One Direction")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','phs')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d %Y"
|
||||
|
||||
@@ -60,11 +60,6 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','pffa')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","My Little Pony")
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
|
||||
@@ -62,11 +62,6 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','prtky') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d/%m/%y" # XXX
|
||||
|
||||
@@ -37,7 +37,6 @@ class PotionsAndSnitchesNetSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
@@ -142,7 +141,7 @@ class PotionsAndSnitchesNetSiteAdapter(BaseSiteAdapter):
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
charstext = [char.string for char in chars]
|
||||
for char in charstext:
|
||||
if char == "!Snape and Harry (required)":
|
||||
if "Snape and Harry (required)" in char:
|
||||
self.story.addToList('characters',"Snape")
|
||||
self.story.addToList('characters',"Harry")
|
||||
else:
|
||||
|
||||
@@ -55,11 +55,6 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','ptdc')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","The Pretender")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d/%m/%y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class PrisonBreakFicNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','pbf')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Prison Break")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d, %Y"
|
||||
@@ -157,6 +152,11 @@ class PrisonBreakFicNetAdapter(BaseSiteAdapter):
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Pairing' in label:
|
||||
ships = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=3')) # XXX
|
||||
for ship in ships:
|
||||
self.story.addToList('ships',ship.string.replace(" and ","/"))
|
||||
|
||||
if 'Genre' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1')) # XXX
|
||||
for genre in genres:
|
||||
|
||||
@@ -55,11 +55,6 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','sda')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Supernatural")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%y"
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Fanficdownloader team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from .. import BeautifulSoup as bs
|
||||
from ..htmlcleanup import stripHTML
|
||||
from .. import exceptions as exceptions
|
||||
|
||||
from base_adapter import BaseSiteAdapter, makeDate
|
||||
|
||||
def getClass():
|
||||
return ScarvesAndCoffeeNetAdapter
|
||||
|
||||
# Class name has to be unique. Our convention is camel case the
|
||||
# sitename with Adapter at the end. www is skipped.
|
||||
class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
|
||||
|
||||
def __init__(self, config, url):
|
||||
BaseSiteAdapter.__init__(self, config, url)
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','scacf')
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
return 'www.scarvesandcoffee.net'
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/viewstory.php?sid=1234"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
|
||||
|
||||
## Login seems to be reasonably standard across eFiction sites.
|
||||
def needToLoginCheck(self, data):
|
||||
if 'Registered Users Only' in data \
|
||||
or 'There is no such account on our website' in data \
|
||||
or "That password doesn't match the one in our database" in data:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# Weirdly, different sites use different warning numbers.
|
||||
# If the title search below fails, there's a good chance
|
||||
# you need a different number. print data at that point
|
||||
# and see what the 'click here to continue' url says.
|
||||
addurl = "&ageconsent=ok&warning=20"
|
||||
else:
|
||||
addurl=""
|
||||
|
||||
# index=1 makes sure we see the story chapter index. Some
|
||||
# sites skip that for one-chapter stories.
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
|
||||
if m != None:
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# We tried the default and still got a warning, so
|
||||
# let's pull the warning number from the 'continue'
|
||||
# link and reload data.
|
||||
addurl = m.group(1)
|
||||
# correct stupid & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL 2nd try: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise exceptions.AdultCheckRequired(self.url)
|
||||
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.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.
|
||||
|
||||
## Title
|
||||
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
|
||||
self.story.setMetadata('title',a.string)
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['href']+addurl))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# eFiction sites don't help us out a lot with their meta data
|
||||
# formating, so it's a little ugly.
|
||||
|
||||
# utility method
|
||||
def defaultGetattr(d,k):
|
||||
try:
|
||||
return d[k]
|
||||
except:
|
||||
return ""
|
||||
|
||||
# <span class="label">Rated:</span> NC-17<br /> etc
|
||||
labels = soup.findAll('span',{'class':'label'})
|
||||
for labelspan in labels:
|
||||
value = labelspan.nextSibling
|
||||
label = labelspan.string
|
||||
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
svalue = ""
|
||||
while not defaultGetattr(value,'class') == 'label':
|
||||
svalue += str(value)
|
||||
value = value.nextSibling
|
||||
self.setDescription(url,svalue)
|
||||
#self.story.setMetadata('description',stripHTML(svalue))
|
||||
|
||||
if 'Rated' in label:
|
||||
self.story.setMetadata('rating', value)
|
||||
|
||||
if 'Word count' in label:
|
||||
self.story.setMetadata('numWords', value)
|
||||
|
||||
if 'Categories' in label:
|
||||
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
|
||||
for cat in cats:
|
||||
self.story.addToList('genre',cat.string)
|
||||
|
||||
if 'Characters' in label:
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Completed' in label:
|
||||
if 'Yes' in value:
|
||||
self.story.setMetadata('status', 'Completed')
|
||||
else:
|
||||
self.story.setMetadata('status', 'In-Progress')
|
||||
|
||||
if 'Published' in label:
|
||||
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
if 'Updated' in label:
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
try:
|
||||
# Find Series name from series URL.
|
||||
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
|
||||
series_name = a.string
|
||||
series_url = 'http://'+self.host+'/'+a['href']
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
|
||||
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
|
||||
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
|
||||
i=1
|
||||
for a in storyas:
|
||||
# skip 'report this' and 'TOC' links
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
break
|
||||
i+=1
|
||||
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
pass
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
logging.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
div = soup.find('div', {'id' : 'story'})
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
@@ -58,7 +58,7 @@ class SG1HeliopolisComAdapter(BaseSiteAdapter):
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
# do. Can't use extracategories, could be Atlantis or SG-1
|
||||
if 'atlantis' in self.story.getMetadata('section'):
|
||||
self.story.addToList("category","Stargate: Atlantis")
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Fanficdownloader team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from .. import BeautifulSoup as bs
|
||||
from ..htmlcleanup import stripHTML
|
||||
from .. import exceptions as exceptions
|
||||
|
||||
from base_adapter import BaseSiteAdapter, makeDate
|
||||
|
||||
def getClass():
|
||||
return SinfulDesireOrgAdapter
|
||||
|
||||
# Class name has to be unique. Our convention is camel case the
|
||||
# sitename with Adapter at the end. www is skipped.
|
||||
class SinfulDesireOrgAdapter(BaseSiteAdapter):
|
||||
|
||||
def __init__(self, config, url):
|
||||
BaseSiteAdapter.__init__(self, config, url)
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/archive/viewstory.php?sid='+self.story.getMetadata('storyId'))
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','snds')
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
return 'www.sinful-desire.org'
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/archive/viewstory.php?sid=1234"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://"+self.getSiteDomain()+"/archive/viewstory.php?sid=")+r"\d+$"
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# Weirdly, different sites use different warning numbers.
|
||||
# If the title search below fails, there's a good chance
|
||||
# you need a different number. print data at that point
|
||||
# and see what the 'click here to continue' url says.
|
||||
addurl = "&warning=5"
|
||||
else:
|
||||
addurl=""
|
||||
|
||||
# index=1 makes sure we see the story chapter index. Some
|
||||
# sites skip that for one-chapter stories.
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
|
||||
if m != None:
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# We tried the default and still got a warning, so
|
||||
# let's pull the warning number from the 'continue'
|
||||
# link and reload data.
|
||||
addurl = m.group(1)
|
||||
# correct stupid & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL 2nd try: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise exceptions.AdultCheckRequired(self.url)
|
||||
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.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.
|
||||
|
||||
## Title
|
||||
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
|
||||
self.story.setMetadata('title',a.string)
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/archive/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/archive/'+chapter['href']+addurl))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# eFiction sites don't help us out a lot with their meta data
|
||||
# formating, so it's a little ugly.
|
||||
|
||||
# utility method
|
||||
def defaultGetattr(d,k):
|
||||
try:
|
||||
return d[k]
|
||||
except:
|
||||
return ""
|
||||
|
||||
# <span class="label">Rated:</span> NC-17<br /> etc
|
||||
labels = soup.findAll('span',{'class':'label'})
|
||||
for labelspan in labels:
|
||||
value = labelspan.nextSibling
|
||||
label = labelspan.string
|
||||
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
svalue = ""
|
||||
while not defaultGetattr(value,'class') == 'label':
|
||||
svalue += str(value)
|
||||
value = value.nextSibling
|
||||
self.setDescription(url,svalue)
|
||||
#self.story.setMetadata('description',stripHTML(svalue))
|
||||
|
||||
if 'Rated' in label:
|
||||
self.story.setMetadata('rating', value)
|
||||
|
||||
if 'Word count' in label:
|
||||
self.story.setMetadata('numWords', value)
|
||||
|
||||
if 'Categories' in label:
|
||||
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
|
||||
for cat in cats:
|
||||
self.story.addToList('category',cat.string)
|
||||
|
||||
if 'Characters' in label:
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Genre' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
|
||||
for genre in genres:
|
||||
self.story.addToList('genre',genre.string)
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=3'))
|
||||
for genre in genres:
|
||||
self.story.addToList('genre',genre.string)
|
||||
|
||||
if 'Warnings' in label:
|
||||
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
|
||||
for warning in warnings:
|
||||
self.story.addToList('warnings',warning.string)
|
||||
|
||||
if 'Completed' in label:
|
||||
if 'Yes' in value:
|
||||
self.story.setMetadata('status', 'Completed')
|
||||
else:
|
||||
self.story.setMetadata('status', 'In-Progress')
|
||||
|
||||
if 'Published' in label:
|
||||
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
if 'Updated' in label:
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
try:
|
||||
# Find Series name from series URL.
|
||||
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
|
||||
series_name = a.string
|
||||
series_url = 'http://'+self.host+'/archive/'+a['href']
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
|
||||
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
|
||||
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
|
||||
i=1
|
||||
for a in storyas:
|
||||
# skip 'report this' and 'TOC' links
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
break
|
||||
i+=1
|
||||
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
pass
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
logging.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
div = soup.find('div', {'id' : 'story'})
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
@@ -56,11 +56,6 @@ class SiyeCoUkAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','siye') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%Y.%m.%d" # XXX
|
||||
|
||||
@@ -55,11 +55,6 @@ class StargateAtlantisOrgAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','stat')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Stargate: Atlantis")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%B %d %Y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class StoriesOfArdaComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','soa')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Lord of the Rings")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@@ -140,25 +140,25 @@ Some more longer description. "I suck at summaries!" "Better than it sounds!"
|
||||
('Chapter 3, Over Cinnabar',self.url+"&chapter=4"),
|
||||
('Chapter 4',self.url+"&chapter=5"),
|
||||
('Chapter 5',self.url+"&chapter=6"),
|
||||
('Chapter 6',self.url+"&chapter=6"),
|
||||
# ('Chapter 7',self.url+"&chapter=6"),
|
||||
# ('Chapter 8',self.url+"&chapter=6"),
|
||||
# ('Chapter 9',self.url+"&chapter=6"),
|
||||
# ('Chapter 0',self.url+"&chapter=6"),
|
||||
# ('Chapter a',self.url+"&chapter=6"),
|
||||
# ('Chapter b',self.url+"&chapter=6"),
|
||||
# ('Chapter c',self.url+"&chapter=6"),
|
||||
# ('Chapter d',self.url+"&chapter=6"),
|
||||
# ('Chapter e',self.url+"&chapter=6"),
|
||||
# ('Chapter f',self.url+"&chapter=6"),
|
||||
# ('Chapter g',self.url+"&chapter=6"),
|
||||
# ('Chapter h',self.url+"&chapter=6"),
|
||||
# ('Chapter i',self.url+"&chapter=6"),
|
||||
# ('Chapter j',self.url+"&chapter=6"),
|
||||
# ('Chapter k',self.url+"&chapter=6"),
|
||||
# ('Chapter l',self.url+"&chapter=6"),
|
||||
# ('Chapter m',self.url+"&chapter=6"),
|
||||
# ('Chapter n',self.url+"&chapter=6"),
|
||||
#('Chapter 6',self.url+"&chapter=7"),
|
||||
#('Chapter 7',self.url+"&chapter=8"),
|
||||
#('Chapter 8',self.url+"&chapter=9"),
|
||||
#('Chapter 9',self.url+"&chapter=0"),
|
||||
#('Chapter 0',self.url+"&chapter=a"),
|
||||
#('Chapter a',self.url+"&chapter=b"),
|
||||
#('Chapter b',self.url+"&chapter=c"),
|
||||
#('Chapter c',self.url+"&chapter=d"),
|
||||
#('Chapter d',self.url+"&chapter=e"),
|
||||
#('Chapter e',self.url+"&chapter=f"),
|
||||
#('Chapter f',self.url+"&chapter=g"),
|
||||
#('Chapter g',self.url+"&chapter=h"),
|
||||
#('Chapter h',self.url+"&chapter=i"),
|
||||
#('Chapter i',self.url+"&chapter=j"),
|
||||
#('Chapter j',self.url+"&chapter=k"),
|
||||
#('Chapter k',self.url+"&chapter=l"),
|
||||
#('Chapter l',self.url+"&chapter=m"),
|
||||
#('Chapter m',self.url+"&chapter=n"),
|
||||
#('Chapter n',self.url+"&chapter=o"),
|
||||
]
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
|
||||
@@ -55,11 +55,6 @@ class TheAlphaGateComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','tag')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Stargate: SG-1")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d %b %Y"
|
||||
|
||||
@@ -55,11 +55,6 @@ class TheHexFilesNetAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','thf')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%Y.%m.%d"
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Fanficdownloader team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from .. import BeautifulSoup as bs
|
||||
from ..htmlcleanup import stripHTML
|
||||
from .. import exceptions as exceptions
|
||||
|
||||
from base_adapter import BaseSiteAdapter, makeDate
|
||||
|
||||
def getClass():
|
||||
return ThePetulantPoetessComAdapter
|
||||
|
||||
# Class name has to be unique. Our convention is camel case the
|
||||
# sitename with Adapter at the end. www is skipped.
|
||||
class ThePetulantPoetessComAdapter(BaseSiteAdapter):
|
||||
|
||||
def __init__(self, config, url):
|
||||
BaseSiteAdapter.__init__(self, config, url)
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId') +'&i=1')
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','tpp')
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%Y/%m/%d"
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
return 'www.thepetulantpoetess.com'
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/viewstory.php?sid=1234"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
|
||||
|
||||
## Login seems to be reasonably standard across eFiction sites.
|
||||
def needToLoginCheck(self, data):
|
||||
if 'You must be a member to read this story.' in data \
|
||||
or "The Ministry of Magic does not have a record of that password. " in data:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def performLogin(self, url):
|
||||
params = {}
|
||||
|
||||
if self.password:
|
||||
params['penname'] = self.username
|
||||
params['password'] = self.password
|
||||
else:
|
||||
params['penname'] = self.getConfig("username")
|
||||
params['password'] = self.getConfig("password")
|
||||
params['cookiecheck'] = '1'
|
||||
params['submit'] = 'Submit'
|
||||
|
||||
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
|
||||
logging.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
|
||||
params['penname']))
|
||||
|
||||
d = self._fetchUrl(loginUrl, params)
|
||||
|
||||
if "My Account Page" not in d : #Member Account
|
||||
logging.info("Failed to login to URL %s as %s" % (loginUrl,
|
||||
params['penname']))
|
||||
raise exceptions.FailedToLogin(url,params['penname'])
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
|
||||
# index=1 makes sure we see the story chapter index. Some
|
||||
# sites skip that for one-chapter stories.
|
||||
url = self.url
|
||||
logging.debug("URL: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
if self.needToLoginCheck(data):
|
||||
# need to log in for this one.
|
||||
self.performLogin(url)
|
||||
data = self._fetchUrl(url)
|
||||
|
||||
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.
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
# Find the chapters:
|
||||
chapters=soup.find('select', {'name' : 'sid'})
|
||||
if chapters == None:
|
||||
self.chapterUrls.append((self.story.getMetadata('title'),url))
|
||||
else:
|
||||
for chapter in chapters.findAll('option', value=re.compile(r"viewstory.php\?sid=\d+&i=1")):
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['value']))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# make sure that the story id is from the first chapter
|
||||
self.story.setMetadata('storyId',self.chapterUrls[0][1].split('=')[1].split('&')[0])
|
||||
|
||||
#locate the story on author's page
|
||||
index = 1
|
||||
found = 0
|
||||
while found == 0:
|
||||
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')+"&page="+str(index)))
|
||||
|
||||
for info in asoup.findAll('td', {'class' : 'highlightcolor1'}):
|
||||
a = info.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
|
||||
if a != None:
|
||||
self.story.setMetadata('title',a.text)
|
||||
found = 1
|
||||
break
|
||||
index=index+1
|
||||
|
||||
# extract metadata
|
||||
b=info.find('b')
|
||||
b.find('a').extract()
|
||||
self.story.setMetadata('rating', b.text.split('[')[1].split(']')[0])
|
||||
|
||||
info = info.findNext('td', {'colspan' : '2'})
|
||||
for label in info.findAll('b'):
|
||||
value = label.nextSibling
|
||||
|
||||
if "Category" in label.text:
|
||||
for cat in info.findAll('a'):
|
||||
self.story.addToList('category',cat.string)
|
||||
|
||||
if "Characters" in label.text:
|
||||
for char in value.split(', '):
|
||||
self.story.addToList('characters',char)
|
||||
|
||||
if "Genres" in label.text:
|
||||
for genre in value.split(', '):
|
||||
if "General" not in genre:
|
||||
self.story.addToList('genre',genre)
|
||||
|
||||
if "Warnings" in label.text:
|
||||
for warning in value.split(', '):
|
||||
if "none" not in warning.lower():
|
||||
self.story.addToList('warnings',warning)
|
||||
|
||||
|
||||
info = info.findNext('td', {'class' : 'tblborder'})
|
||||
info.find('b').extract()
|
||||
self.setDescription(url,info)
|
||||
|
||||
info = info.findNext('td', {'class' : 'highlightcolor2'})
|
||||
for label in info.findAll('b'):
|
||||
value = label.nextSibling
|
||||
|
||||
if "Completed" in label.text:
|
||||
if 'Yes' in value:
|
||||
self.story.setMetadata('status', 'Completed')
|
||||
else:
|
||||
self.story.setMetadata('status', 'In-Progress')
|
||||
|
||||
if 'Published' in label.text:
|
||||
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
if 'Updated' in label.text:
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
if 'Word Count' in label.text:
|
||||
self.story.setMetadata('numWords', value)
|
||||
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
logging.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
div = soup.findAll('table')[2].findAll('td')[1]
|
||||
for a in div.findAll('div'):
|
||||
a.extract()
|
||||
div.name='div'
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
@@ -60,11 +60,6 @@ class TheQuidditchPitchOrgAdapter(BaseSiteAdapter): # XXX
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','tqdpch') # XXX
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter") # XXX
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y" # XXX
|
||||
|
||||
@@ -55,11 +55,6 @@ class TwilightArchivesComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','twa')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Twilight")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d %b %y"
|
||||
|
||||
@@ -37,7 +37,6 @@ class TwilightedNetSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Twilight")
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class TwiwriteNetSiteAdapter(BaseSiteAdapter):
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.story.addToList("category","Twilight")
|
||||
self.is_adult = False
|
||||
self.username = "NoneGiven" # if left empty, twiwrite.net doesn't return any message at all.
|
||||
self.password = ""
|
||||
|
||||
@@ -53,11 +53,6 @@ class WalkingThePlankOrgAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','wtp')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Harry Potter")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%b %d, %Y"
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2012 Fanficdownloader team
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import urllib2
|
||||
|
||||
from .. import BeautifulSoup as bs
|
||||
from ..htmlcleanup import stripHTML
|
||||
from .. import exceptions as exceptions
|
||||
|
||||
from base_adapter import BaseSiteAdapter, makeDate
|
||||
|
||||
def getClass():
|
||||
return WolverineAndRogueComAdapter
|
||||
|
||||
# Class name has to be unique. Our convention is camel case the
|
||||
# sitename with Adapter at the end. www is skipped.
|
||||
class WolverineAndRogueComAdapter(BaseSiteAdapter):
|
||||
|
||||
def __init__(self, config, url):
|
||||
BaseSiteAdapter.__init__(self, config, url)
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
# Most sites that claim to be
|
||||
# iso-8859-1 (and some that claim to be
|
||||
# utf8) are really windows-1252.
|
||||
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
|
||||
self.password = ""
|
||||
self.is_adult=False
|
||||
|
||||
# get storyId from url--url validation guarantees query is only sid=1234
|
||||
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
|
||||
logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
|
||||
|
||||
# normalized story URL.
|
||||
self._setURL('http://' + self.getSiteDomain() + '/wrfa/viewstory.php?sid='+self.story.getMetadata('storyId'))
|
||||
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','wrfa')
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%m/%d/%Y"
|
||||
|
||||
@staticmethod # must be @staticmethod, don't remove it.
|
||||
def getSiteDomain():
|
||||
# The site domain. Does have www here, if it uses it.
|
||||
return 'www.wolverineandrogue.com'
|
||||
|
||||
def getSiteExampleURLs(self):
|
||||
return "http://"+self.getSiteDomain()+"/wrfa/viewstory.php?sid=1234"
|
||||
|
||||
def getSiteURLPattern(self):
|
||||
return re.escape("http://"+self.getSiteDomain()+"/wrfa/viewstory.php?sid=")+r"\d+$"
|
||||
|
||||
|
||||
## Getting the chapter list and the meta data, plus 'is adult' checking.
|
||||
def extractChapterUrlsAndMetadata(self):
|
||||
|
||||
# index=1 makes sure we see the story chapter index. Some
|
||||
# sites skip that for one-chapter stories.
|
||||
url = self.url+'&index=1'
|
||||
logging.debug("URL: "+url)
|
||||
|
||||
try:
|
||||
data = self._fetchUrl(url)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.code == 404:
|
||||
raise exceptions.StoryDoesNotExist(self.url)
|
||||
else:
|
||||
raise e
|
||||
|
||||
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
|
||||
raise exceptions.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.
|
||||
|
||||
## Title
|
||||
pt = soup.find('div', {'id' : 'pagetitle'})
|
||||
a = pt.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
|
||||
self.story.setMetadata('title',a.text)
|
||||
|
||||
# Find authorid and URL from... author url.
|
||||
a = pt.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
|
||||
self.story.setMetadata('authorId',a['href'].split('=')[1])
|
||||
self.story.setMetadata('authorUrl','http://'+self.host+'/wrfa/'+a['href'])
|
||||
self.story.setMetadata('author',a.string)
|
||||
|
||||
rating=pt.text.split('(')[1].split(')')[0]
|
||||
self.story.setMetadata('rating', rating)
|
||||
|
||||
# Find the chapters:
|
||||
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
|
||||
# just in case there's tags, like <i> in chapter titles.
|
||||
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/wrfa/'+chapter['href']))
|
||||
|
||||
self.story.setMetadata('numChapters',len(self.chapterUrls))
|
||||
|
||||
# eFiction sites don't help us out a lot with their meta data
|
||||
# formating, so it's a little ugly.
|
||||
|
||||
# <span class="label">Rated:</span> NC-17<br /> etc
|
||||
content=soup.find('div',{'class' : 'content'})
|
||||
labels = soup.findAll('span',{'class':'label'})
|
||||
|
||||
value = labels[0].previousSibling
|
||||
svalue = ""
|
||||
while value != None:
|
||||
val = value
|
||||
value = value.previousSibling
|
||||
while "Categories" not in str(val):
|
||||
svalue += str(val)
|
||||
val = val.nextSibling
|
||||
self.setDescription(url,svalue)
|
||||
|
||||
|
||||
for labelspan in labels:
|
||||
value = labelspan.nextSibling
|
||||
label = labelspan.string
|
||||
|
||||
if 'Rated' in label:
|
||||
self.story.setMetadata('rating', value)
|
||||
|
||||
if 'Word count' in label:
|
||||
self.story.setMetadata('numWords', value.split(' -')[0])
|
||||
|
||||
if 'Categories' in label:
|
||||
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
|
||||
for cat in cats:
|
||||
self.story.addToList('category',cat.string)
|
||||
|
||||
if 'Characters' in label:
|
||||
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
|
||||
for char in chars:
|
||||
self.story.addToList('characters',char.string)
|
||||
|
||||
if 'Genre' in label:
|
||||
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
|
||||
for genre in genres:
|
||||
self.story.addToList('genre',genre.string)
|
||||
|
||||
if 'Warnings' in label:
|
||||
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
|
||||
for warning in warnings:
|
||||
self.story.addToList('warnings',warning.string)
|
||||
|
||||
if 'Complete' in label:
|
||||
if 'Yes' in value:
|
||||
self.story.setMetadata('status', 'Completed')
|
||||
else:
|
||||
self.story.setMetadata('status', 'In-Progress')
|
||||
|
||||
if 'Published' in label:
|
||||
self.story.setMetadata('datePublished', makeDate(value.split(' -')[0], self.dateformat))
|
||||
|
||||
if 'Updated' in label:
|
||||
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
|
||||
|
||||
try:
|
||||
# Find Series name from series URL.
|
||||
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
|
||||
series_name = a.string
|
||||
series_url = 'http://'+self.host+'/wrfa/'+a['href']
|
||||
|
||||
# use BeautifulSoup HTML parser to make everything easier to find.
|
||||
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
|
||||
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
|
||||
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
|
||||
i=1
|
||||
for a in storyas:
|
||||
# skip 'report this' and 'TOC' links
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
self.setSeries(series_name, i)
|
||||
break
|
||||
i+=1
|
||||
|
||||
except:
|
||||
# I find it hard to care if the series parsing fails
|
||||
pass
|
||||
|
||||
# grab the text for an individual chapter.
|
||||
def getChapterText(self, url):
|
||||
|
||||
logging.debug('Getting chapter text from: %s' % url)
|
||||
|
||||
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
|
||||
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
|
||||
|
||||
div = soup.find('div', {'id' : 'story'})
|
||||
|
||||
if None == div:
|
||||
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
|
||||
|
||||
return self.utf8FromSoup(url,div)
|
||||
@@ -56,11 +56,6 @@ class WraithBaitComAdapter(BaseSiteAdapter):
|
||||
# Each adapter needs to have a unique site abbreviation.
|
||||
self.story.setMetadata('siteabbrev','wb')
|
||||
|
||||
# If all stories from the site fall into the same category,
|
||||
# the site itself isn't likely to label them as such, so we
|
||||
# do.
|
||||
self.story.addToList("category","Stargate")
|
||||
|
||||
# The date format will vary from site to site.
|
||||
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
|
||||
self.dateformat = "%d %b %Y"
|
||||
|
||||
@@ -41,7 +41,7 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
# targeted as us and offered to 'whitelist our IP'. Clearly,
|
||||
# that wouldn't work, but it does let me do this in good
|
||||
# conscience:
|
||||
self.opener.addheaders = [('User-agent', 'FFDL/1.5')]
|
||||
self.opener.addheaders = [('User-agent', 'FFDL/1.6')]
|
||||
|
||||
self.decode = ["Windows-1252",
|
||||
"utf8"] # 1252 is a superset of iso-8859-1.
|
||||
@@ -115,7 +115,7 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
# viewstory.php?sid=1654&ageconsent=ok&warning=5
|
||||
#print data
|
||||
#m = re.search(r"'viewstory.php\?sid=1882(&warning=4)'",data)
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
|
||||
m = re.search(r"'viewstory.php\?sid=\d+((&ageconsent=ok)?&warning=\d+)'",data)
|
||||
if m != None:
|
||||
if self.is_adult or self.getConfig("is_adult"):
|
||||
# We tried the default and still got a warning, so
|
||||
@@ -123,7 +123,8 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
# link and reload data.
|
||||
addurl = m.group(1)
|
||||
# correct stupid & error in url.
|
||||
addurl = addurl.replace("&","&")
|
||||
# explicitly put ageconsent because google appengine regexp doesn't include it for some reason.
|
||||
addurl = addurl.replace("&","&")+'&ageconsent=ok'
|
||||
url = self.url+'&index=1'+addurl
|
||||
logging.debug("URL 2nd try: "+url)
|
||||
|
||||
@@ -140,9 +141,17 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
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.")
|
||||
|
||||
# because for some reason, this works while simple 'print data' errors on ascii conversion.
|
||||
# loopdata = data
|
||||
# chklen=5000
|
||||
# while len(loopdata) > 0:
|
||||
# if len(loopdata) < 5000:
|
||||
# chklen = len(loopdata)
|
||||
# logging.info("loopdata: %s" % loopdata[:chklen])
|
||||
# loopdata = loopdata[chklen:]
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -182,9 +191,11 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
if 'Summary' in label:
|
||||
## Everything until the next span class='label'
|
||||
svalue = ""
|
||||
while not defaultGetattr(value,'class') == 'label':
|
||||
while value and not defaultGetattr(value,'class') == 'label':
|
||||
svalue += str(value)
|
||||
value = value.nextSibling
|
||||
# sometimes poorly formated desc (<p> w/o </p>) leads
|
||||
# to all labels being included.
|
||||
self.setDescription(url,svalue)
|
||||
#self.story.setMetadata('description',stripHTML(svalue))
|
||||
|
||||
@@ -245,7 +256,6 @@ class YourFanfictionComAdapter(BaseSiteAdapter):
|
||||
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
|
||||
i=1
|
||||
for a in storyas:
|
||||
print("series a['href']:%s"%a['href'])
|
||||
# skip 'report this' and 'TOC' links
|
||||
if 'contact.php' not in a['href'] and 'index' not in a['href']:
|
||||
if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
|
||||
|
||||
@@ -89,6 +89,7 @@ class BaseSiteAdapter(Configurable):
|
||||
self.oldimgs = None
|
||||
self.oldcover = None # (data of existing cover html, data of existing cover image)
|
||||
self.calibrebookmark = None
|
||||
self.logfile = None
|
||||
## order of preference for decoding.
|
||||
self.decode = ["utf8",
|
||||
"Windows-1252"] # 1252 is a superset of
|
||||
@@ -229,6 +230,8 @@ class BaseSiteAdapter(Configurable):
|
||||
# cheesy way to carry calibre bookmark file forward across update.
|
||||
if self.calibrebookmark:
|
||||
self.story.calibrebookmark = self.calibrebookmark
|
||||
if self.logfile:
|
||||
self.story.logfile = self.logfile
|
||||
|
||||
return self.story
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ def get_update_data(inputio,
|
||||
|
||||
oldcover = None
|
||||
calibrebookmark = None
|
||||
logfile = None
|
||||
# Looking for pre-existing cover.
|
||||
for item in contentdom.getElementsByTagName("reference"):
|
||||
if item.getAttribute("type") == "cover":
|
||||
@@ -96,6 +97,11 @@ def get_update_data(inputio,
|
||||
if( item.getAttribute("media-type") == "application/xhtml+xml" ):
|
||||
href=relpath+item.getAttribute("href")
|
||||
#print("---- item href:%s path part: %s"%(href,get_path_part(href)))
|
||||
if re.match(r'.*/log_page\.x?html',href):
|
||||
try:
|
||||
logfile = epub.read(href).decode("utf-8")
|
||||
except:
|
||||
pass # corner case I bumped into while testing.
|
||||
if re.match(r'.*/(file|chapter)\d+\.x?html',href):
|
||||
if getsoups:
|
||||
soup = bs.BeautifulSoup(epub.read(href).decode("utf-8"))
|
||||
@@ -136,7 +142,7 @@ def get_update_data(inputio,
|
||||
|
||||
for k in images.keys():
|
||||
print("\tlongdesc:%s\n\tData len:%s\n"%(k,len(images[k])))
|
||||
return (source,filecount,soups,images,oldcover,calibrebookmark)
|
||||
return (source,filecount,soups,images,oldcover,calibrebookmark,logfile)
|
||||
|
||||
def get_path_part(n):
|
||||
relpath = os.path.dirname(n)
|
||||
|
||||
@@ -25,14 +25,38 @@ from gziphttp import GZipProcessor
|
||||
|
||||
import adapters
|
||||
|
||||
def get_urls_from_page(url):
|
||||
|
||||
opener = u2.build_opener(u2.HTTPCookieProcessor(),GZipProcessor())
|
||||
soup = BeautifulSoup(opener.open(url).read())
|
||||
|
||||
def get_urls_from_page(url,config=None):
|
||||
|
||||
normalized = set() # normalized url
|
||||
retlist = [] # orig urls.
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
if not config:
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
|
||||
data = None
|
||||
|
||||
# special stuff to log into archiveofourown.org, if possible.
|
||||
# Unlike most that show the links to 'adult' stories, but protect
|
||||
# them, AO3 doesn't even show them if not logged in. Only works
|
||||
# with saved user/pass--not going to prompt for list.
|
||||
if 'archiveofourown.org' in url:
|
||||
ao3adapter = adapters.getAdapter(config,"http://www.archiveofourown.org/works/0","EPUB")
|
||||
if ao3adapter.getConfig("username"):
|
||||
if ao3adapter.getConfig("is_adult"):
|
||||
addurl = "?view_adult=true"
|
||||
else:
|
||||
addurl=""
|
||||
# just to get an authenticity_token.
|
||||
data = ao3adapter._fetchUrl(url+addurl)
|
||||
# login the session.
|
||||
ao3adapter.performLogin(url,data)
|
||||
# get the list page with logged in session.
|
||||
data = ao3adapter._fetchUrl(url)
|
||||
|
||||
if not data:
|
||||
opener = u2.build_opener(u2.HTTPCookieProcessor(),GZipProcessor())
|
||||
data = opener.open(url).read()
|
||||
|
||||
soup = BeautifulSoup(data)
|
||||
|
||||
for a in soup.findAll('a'):
|
||||
if a.has_key('href'):
|
||||
|
||||
@@ -201,6 +201,7 @@ class Story:
|
||||
self.cover=None # *href* of new cover image--need to create html.
|
||||
self.oldcover=None # (oldcoverhtmlhref,oldcoverhtmltype,oldcoverhtmldata,oldcoverimghref,oldcoverimgtype,oldcoverimgdata)
|
||||
self.calibrebookmark=None # cheesy way to carry calibre bookmark file forward across update.
|
||||
self.logfile=None # cheesy way to carry log file forward across update.
|
||||
|
||||
def setMetadata(self, key, value):
|
||||
## still keeps < < and &
|
||||
@@ -224,12 +225,14 @@ class Story:
|
||||
def getMetadata(self, key, removeallentities=False, doreplacements=True):
|
||||
value = None
|
||||
if self.isList(key):
|
||||
value = ', '.join(self.getList(key))
|
||||
value = ', '.join(self.getList(key, removeallentities))
|
||||
elif self.metadata.has_key(key):
|
||||
value = self.metadata[key]
|
||||
if value:
|
||||
if key == "numWords":
|
||||
value = commaGroups(value)
|
||||
if key == "numChapters":
|
||||
value = commaGroups("%d"%value)
|
||||
if key == "dateCreated":
|
||||
value = value.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if key == "datePublished" or key == "dateUpdated":
|
||||
@@ -279,7 +282,7 @@ class Story:
|
||||
# just for less clutter in adapters.
|
||||
def extendList(self,listname,l):
|
||||
for v in l:
|
||||
self.addToList(listname,v)
|
||||
self.addToList(listname,v.strip())
|
||||
|
||||
def addToList(self,listname,value):
|
||||
if value==None:
|
||||
@@ -307,10 +310,10 @@ class Story:
|
||||
|
||||
return retlist
|
||||
|
||||
def getLists(self):
|
||||
def getLists(self, removeallentities=False):
|
||||
lsts = {}
|
||||
for ln in self.listables.keys():
|
||||
lsts[ln] = self.getList(ln)
|
||||
lsts[ln] = self.getList(ln, removeallentities)
|
||||
return lsts
|
||||
|
||||
def isList(self,listname):
|
||||
|
||||
@@ -50,6 +50,7 @@ class BaseStoryWriter(Configurable):
|
||||
'genre',
|
||||
'language',
|
||||
'characters',
|
||||
'ships',
|
||||
'series',
|
||||
'status',
|
||||
'datePublished',
|
||||
@@ -81,6 +82,7 @@ class BaseStoryWriter(Configurable):
|
||||
'status':'Status',
|
||||
'series':'Series',
|
||||
'characters':'Characters',
|
||||
'ships':'Relationships',
|
||||
'datePublished':'Published',
|
||||
'dateUpdated':'Updated',
|
||||
'dateCreated':'Packaged',
|
||||
@@ -104,12 +106,19 @@ class BaseStoryWriter(Configurable):
|
||||
}
|
||||
self.story.setMetadata('formatname',self.getFormatName())
|
||||
self.story.setMetadata('formatext',self.getFormatExt())
|
||||
|
||||
for tag in self.getConfigList("extratags"):
|
||||
self.story.addToList("extratags",tag)
|
||||
|
||||
def getMetadata(self,key):
|
||||
return stripHTML(self.story.getMetadata(key))
|
||||
## Look for config parameter, split and add each to metadata field.
|
||||
for (config,metadata) in [("extratags","extratags"),
|
||||
("extracategories","category"),
|
||||
("extragenres","genre"),
|
||||
("extracharacters","characters"),
|
||||
("extraships","ships"),
|
||||
("extrawarnings","warnings")]:
|
||||
for val in self.getConfigList(config):
|
||||
self.story.addToList(metadata,val)
|
||||
|
||||
def getMetadata(self,key, removeallentities=False):
|
||||
return stripHTML(self.story.getMetadata(key, removeallentities))
|
||||
|
||||
def getOutputFileName(self):
|
||||
if self.getConfig('zip_output'):
|
||||
@@ -252,7 +261,7 @@ class BaseStoryWriter(Configurable):
|
||||
if close:
|
||||
outstream.close()
|
||||
|
||||
def getTags(self):
|
||||
def getTags(self, removeallentities=False):
|
||||
# set to avoid duplicates subject tags.
|
||||
subjectset = set()
|
||||
|
||||
@@ -265,10 +274,10 @@ class BaseStoryWriter(Configurable):
|
||||
if entry in self.getConfigList("include_subject_tags") and \
|
||||
entry not in self.story.getLists() and \
|
||||
self.story.getMetadata(entry):
|
||||
subjectset.add(self.getMetadata(entry))
|
||||
subjectset.add(self.getMetadata(entry, removeallentities))
|
||||
|
||||
# listables all go into dc:subject tags, but only if they are configured.
|
||||
for (name,lst) in self.story.getLists().iteritems():
|
||||
for (name,lst) in self.story.getLists(removeallentities).iteritems():
|
||||
if name in self.getConfigList("include_subject_tags"):
|
||||
for tag in lst:
|
||||
subjectset.add(tag)
|
||||
|
||||
@@ -140,6 +140,101 @@ ${value}<br />
|
||||
</html>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Update Log</title>
|
||||
<link href="stylesheet.css" type="text/css" charset="UTF-8" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Update Log</h3>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_ENTRY = string.Template('''
|
||||
<b>${label}:</b> <span id="${id}">${value}</span>
|
||||
''')
|
||||
|
||||
self.EPUB_LOG_PAGE_END = string.Template('''
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
|
||||
def writeLogPage(self, out):
|
||||
"""
|
||||
XXX
|
||||
|
||||
|
||||
Write the log page, but only include entries that there's
|
||||
metadata for. START, ENTRY and END are expected to already by
|
||||
string.Template(). START and END are expected to use the same
|
||||
names as Story.metadata, but ENTRY should use id, label and value.
|
||||
"""
|
||||
if self.getConfig("include_logpage"):
|
||||
|
||||
# if there's a self.story.logfile, there's an existing log
|
||||
# to add to.
|
||||
if self.story.logfile:
|
||||
print("existing logfile found, appending")
|
||||
print("existing data:%s"%self._getLastLogData(self.story.logfile))
|
||||
replace_string = "</body>" # "</h3>"
|
||||
self._write(out,self.story.logfile.replace(replace_string,self._makeLogEntry(self._getLastLogData(self.story.logfile))+replace_string))
|
||||
else:
|
||||
# otherwise, write a new one.
|
||||
self._write(out,self.EPUB_LOG_PAGE_START.substitute(self.story.getAllMetadata()))
|
||||
self._write(out,self._makeLogEntry())
|
||||
self._write(out,self.EPUB_LOG_PAGE_END.substitute(self.story.getAllMetadata()))
|
||||
|
||||
# self parsing instead of Soup because it should be simple and not
|
||||
# worth the overhead.
|
||||
def _getLastLogData(self,logfile):
|
||||
"""
|
||||
Make a dict() of the most recent(last) log entry for each piece of metadata.
|
||||
Switch rindex to index to search from top instead of bottom.
|
||||
"""
|
||||
values = {}
|
||||
for entry in self.getConfigList("logpage_entries"):
|
||||
if entry in self.validEntries:
|
||||
try:
|
||||
# <span id="dateUpdated">1975-04-15</span>
|
||||
span = '<span id="%s">'%entry
|
||||
idx = logfile.rindex(span)+len(span)
|
||||
values[entry] = logfile[idx:logfile.index('</span>',idx)]
|
||||
except Exception, e:
|
||||
#print("e:%s"%e)
|
||||
pass
|
||||
|
||||
return values
|
||||
|
||||
def _makeLogEntry(self, oldvalues={}):
|
||||
retval = "<p class='log_entry'>"
|
||||
|
||||
for entry in self.getConfigList("logpage_entries"):
|
||||
if entry in self.validEntries:
|
||||
val = self.story.getMetadata(entry)
|
||||
if val and ( entry not in oldvalues or val != oldvalues[entry] ):
|
||||
if self.hasConfig(entry+"_label"):
|
||||
label=self.getConfig(entry+"_label")
|
||||
else:
|
||||
print("Using fallback label for %s_label"%entry)
|
||||
label=self.titleLabels[entry]
|
||||
|
||||
retval = retval + self.EPUB_LOG_ENTRY.substitute({'id':entry,
|
||||
'label':label,
|
||||
'value':val})
|
||||
else:
|
||||
# could be useful for introducing extra text, but
|
||||
# mostly it makes it easy to tell when you get the
|
||||
# keyword wrong.
|
||||
retval = retval + entry
|
||||
|
||||
retval = retval + "</p><hr />"
|
||||
|
||||
if self.getConfig('replace_hr'):
|
||||
retval = retval.replace("<hr />","<div class='center'>* * *</div>")
|
||||
|
||||
return retval
|
||||
|
||||
def writeStoryImpl(self, out):
|
||||
|
||||
## Python 2.5 ZipFile is rather more primative than later
|
||||
@@ -349,6 +444,11 @@ div { margin: 0pt; padding: 0pt; }
|
||||
if len(self.story.getChapters()) > 1 and self.getConfig("include_tocpage") and not self.metaonly :
|
||||
items.append(("toc_page","OEBPS/toc_page.xhtml","application/xhtml+xml","Table of Contents"))
|
||||
itemrefs.append("toc_page")
|
||||
|
||||
if self.getConfig("include_logpage"):
|
||||
items.append(("log_page","OEBPS/log_page.xhtml","application/xhtml+xml","Update Log"))
|
||||
itemrefs.append("log_page")
|
||||
|
||||
for index, (title,html) in enumerate(self.story.getChapters()):
|
||||
if html:
|
||||
i=index+1
|
||||
@@ -480,6 +580,13 @@ div { margin: 0pt; padding: 0pt; }
|
||||
outputepub.writestr("OEBPS/toc_page.xhtml",tocpageIO.getvalue())
|
||||
tocpageIO.close()
|
||||
|
||||
# write log page.
|
||||
logpageIO = StringIO.StringIO()
|
||||
self.writeLogPage(logpageIO)
|
||||
if logpageIO.getvalue(): # will be false if no log page.
|
||||
outputepub.writestr("OEBPS/log_page.xhtml",logpageIO.getvalue())
|
||||
logpageIO.close()
|
||||
|
||||
for index, (title,html) in enumerate(self.story.getChapters()):
|
||||
if html:
|
||||
logging.debug('Writing chapter text for: %s' % title)
|
||||
|
||||
+28
-5
@@ -54,11 +54,9 @@
|
||||
much easier. </p>
|
||||
</div>
|
||||
<!-- put announcements here, h3 is a good title size. -->
|
||||
<h3>New Sites</h3>
|
||||
<h3>New Feature</h3>
|
||||
<p>
|
||||
|
||||
New sites grangerenchanted.com, hlfiction.net and nha.magical-worlds.us.
|
||||
<br />Thanks, Ida!
|
||||
FFDL now records 'Relationships' as metadata for those sites that provide it.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
@@ -69,7 +67,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-20.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
<a href="http://4-4-23.fanfictiondownloader.appspot.com">Previous Version</a> is also available for you to use if necessary.
|
||||
</p>
|
||||
<div id='error'>
|
||||
{{ error_message }}
|
||||
@@ -506,6 +504,31 @@
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://nha.magical-worlds.us/viewstory.php?sid=1234">http://nha.magical-worlds.us/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
<dt>www.dracoandginny.com</dt>
|
||||
<dd>
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://www.dracoandginny.com/viewstory.php?sid=1234">http://www.dracoandginny.com/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
<dt>www.scarvesandcoffee.net</dt>
|
||||
<dd>
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://www.scarvesandcoffee.net/viewstory.php?sid=1234">http://www.scarvesandcoffee.net/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
<dt>www.thepetulantpoetess.com</dt>
|
||||
<dd>
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://www.thepetulantpoetess.com/viewstory.php?sid=1234">http://www.thepetulantpoetess.com/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
<dt>www.wolverineandrogue.com</dt>
|
||||
<dd>
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://www.wolverineandrogue.com/wrfa/viewstory.php?sid=1234">http://www.wolverineandrogue.com/wrfa/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
<dt>www.sinful-desire.org</dt>
|
||||
<dd>
|
||||
Use the URL of the story's chapter list, such as
|
||||
<br /><a href="http://www.sinful-desire.org/archive/viewstory.php?sid=1234">http://www.sinful-desire.org/archive/viewstory.php?sid=1234</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>
|
||||
A few additional things to know, which will make your life substantially easier:
|
||||
|
||||
+236
-7
@@ -19,8 +19,7 @@
|
||||
## overridden at several levels
|
||||
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
## adult content. Uncomment by removing '#' in front of is_adult.
|
||||
#is_adult:true
|
||||
|
||||
## All available titlepage_entries and the label used for them:
|
||||
@@ -43,6 +42,7 @@ category_label:Category
|
||||
genre_label:Genre
|
||||
language_label:Language
|
||||
characters_label:Characters
|
||||
ships_label:Relationships
|
||||
series_label:Series
|
||||
## Completed/In-Progress
|
||||
status_label:Status
|
||||
@@ -75,7 +75,7 @@ version_label:FFDL Version
|
||||
## 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,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,numWords,site,description
|
||||
titlepage_entries: series,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
|
||||
@@ -101,11 +101,20 @@ include_tocpage: true
|
||||
|
||||
## entries to make epub subjects and calibre tags
|
||||
## lastupdate creates two tags: "Last Update Year/Month: %Y/%m" and "Last Update: %Y/%m/%d"
|
||||
include_subject_tags: extratags, genre, category, characters, status
|
||||
include_subject_tags: extratags, genre, category, characters, ships, status
|
||||
|
||||
## extra tags (comma separated) to include, primarily for epub.
|
||||
extratags: FanFiction
|
||||
|
||||
## extra categories, genres, characters, ships and warnings can be
|
||||
## configured. Used primarily for sites that are dedicated to a genre
|
||||
## or 'ship and so don't included it for every story.
|
||||
#extracategories:
|
||||
#extragenres:
|
||||
#extracharacters:
|
||||
#extraships:
|
||||
#extrawarnings:
|
||||
|
||||
## number of seconds to sleep between calls to the story site. May by
|
||||
## useful if pulling large numbers of stories or if the site is slow.
|
||||
#slow_down_sleep_time:0.5
|
||||
@@ -183,9 +192,24 @@ windows_eol: true
|
||||
[epub]
|
||||
|
||||
## epub carries the TOC in metadata.
|
||||
## mobi generated from epub will have a TOC at the end.
|
||||
## mobi generated from epub by calibre will have a TOC at the end.
|
||||
include_tocpage: false
|
||||
|
||||
## include a Update Log page before the story text. If included, the
|
||||
## log will be updated each time the epub is an all the metadata
|
||||
## fields that have changed since the last update (typically
|
||||
## dateUpdated,numChapters,numWords at a minimum) will be shown.
|
||||
## Great for tracking when chapters came out and when the description,
|
||||
## etc changed.
|
||||
include_logpage: false
|
||||
|
||||
## items to include in the log page Empty metadata entries, or those
|
||||
## that haven't changed since the last update, will *not* appear, even
|
||||
## 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
|
||||
|
||||
## epub->mobi conversions typically don't like tables.
|
||||
titlepage_use_table: false
|
||||
|
||||
@@ -200,10 +224,12 @@ background_color: ffffff
|
||||
## Allow customization of CSS. Make sure to keep at least one space
|
||||
## at the start of each line and to escape % to %%. Also need
|
||||
## background_color to be in the same section, if included in CSS.
|
||||
## 'adobe-text-layout: optimizeSpeed;' prevents hyphenation on newer Nooks
|
||||
output_css:
|
||||
body { background-color: #%(background_color)s;
|
||||
text-align: justify;
|
||||
margin: 2%%; }
|
||||
margin: 2%%;
|
||||
adobe-text-layout: optimizeSpeed; }
|
||||
pre { font-size: x-small; }
|
||||
sml { font-size: small; }
|
||||
h1 { text-align: center; }
|
||||
@@ -287,6 +313,11 @@ nook_img_fix:true
|
||||
## URLs like: http://test1.com?sid=12345
|
||||
[test1.com]
|
||||
extratags: FanFiction,Testing
|
||||
extracategories:Fafner
|
||||
extragenres:Romance,Fluff
|
||||
extracharacters:Reginald Smythe-Smythe,Mokona
|
||||
extraships:Smythe-Smythe/Mokona
|
||||
extrawarnings:Extreme Bogosity
|
||||
|
||||
## If necessary, you can define [<site>:<format>] sections to
|
||||
## customize the formats differently for the same site. Overrides
|
||||
@@ -313,6 +344,11 @@ extratags: FanFiction,Testing,HTML
|
||||
#is_adult:true
|
||||
|
||||
[ashwinder.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape,Hermione Granger
|
||||
extraships:Severus Snape/Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -321,6 +357,9 @@ extratags: FanFiction,Testing,HTML
|
||||
#password:yourpassword
|
||||
|
||||
[castlefans.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Castle
|
||||
|
||||
## 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
|
||||
@@ -344,6 +383,11 @@ extratags: FanFiction,Testing,HTML
|
||||
cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
|
||||
[dark-solace.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Buffy: The Vampire Slayer
|
||||
extracharacters:Buffy, Spike
|
||||
extraships:Spike/Buffy
|
||||
|
||||
## 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
|
||||
@@ -352,6 +396,11 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[dramione.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Hermione Granger
|
||||
extraships:Draco Malfoy/Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -365,6 +414,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[erosnsappho.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -375,6 +427,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
|
||||
[fanfiction.mugglenet.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -388,6 +443,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[fanfiction.portkey.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extraships:Harry Potter/Hermione Granger
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -410,6 +469,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[grangerenchanted.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Hermione Granger
|
||||
|
||||
## 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
|
||||
@@ -423,26 +486,51 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[hlfiction.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Highlander
|
||||
|
||||
[ksarchive.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Star Trek
|
||||
extracharacters:Kirk,Spock
|
||||
extraships:Kirk/Spock
|
||||
|
||||
[lumos.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[national-library.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:West Wing
|
||||
|
||||
[ncisfic.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
[nfacommunity.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[nha.magical-worlds.us]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Buffy: The Vampire Slayer
|
||||
extracharacters:Willow
|
||||
|
||||
[occlumency.sycophanthex.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape
|
||||
|
||||
## 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
|
||||
@@ -451,6 +539,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#password:yourpassword
|
||||
|
||||
[onedirectionfanfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:One Direction
|
||||
|
||||
## 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
|
||||
@@ -464,18 +555,28 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[ponyfictionarchive.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:My Little Pony: Friendship is Magic
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[pretendercentre.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:The Pretender
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[samdean.archive.nu]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Supernatural
|
||||
extracharacters:Sam,Dean
|
||||
extraships:Sam/Dean
|
||||
|
||||
[sg1-heliopolis.com]
|
||||
## Some sites do not require a login, but do require the user to
|
||||
@@ -484,6 +585,8 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[stargate-atlantis.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: Atlantis
|
||||
|
||||
[svufiction.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -499,6 +602,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[thehexfiles.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Harry Potter
|
||||
extraships:Harry Potter/Draco Malfoy
|
||||
|
||||
[themasque.net]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -514,6 +621,9 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[thequidditchpitch.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -527,14 +637,40 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[www.adastrafanfic.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Star Trek
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.dracoandginny.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Draco Malfoy,Ginny Weasley
|
||||
extraships:Draco Malfoy/Ginny Weasley
|
||||
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.thealphagate.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: SG-1
|
||||
|
||||
[www.checkmated.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## 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
|
||||
@@ -549,6 +685,10 @@ cover_exclusion_regexp:/images/.*?ribbon.gif
|
||||
#is_adult:true
|
||||
|
||||
[www.dokuga.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:InuYasha
|
||||
extracharacters:Sesshoumaru,Kagome
|
||||
extraships:Sesshoumaru/Kagome
|
||||
|
||||
[www.fanfiction.net]
|
||||
## fanfiction.net's 'cover' images are really just tiny thumbnails.
|
||||
@@ -566,6 +706,9 @@ never_make_cover: true
|
||||
[www.ficbook.net]
|
||||
|
||||
[www.fictionalley.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -584,6 +727,9 @@ extratags:
|
||||
#password:yourpassword
|
||||
|
||||
[www.fimfiction.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:My Little Pony: Friendship is Magic
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -595,20 +741,33 @@ extratags:
|
||||
#fail_on_password: false
|
||||
|
||||
[www.harrypotterfanfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.hpfandom.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.hpfanficarchive.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
[www.ik-eternal.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:InuYasha
|
||||
extracharacters:InuYasha,Kagome
|
||||
extraships:InuYasha/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
|
||||
@@ -622,6 +781,9 @@ extratags:
|
||||
#is_adult:true
|
||||
|
||||
[www.libraryofmoria.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Lord of the Rings
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -630,6 +792,9 @@ extratags:
|
||||
[www.mediaminer.org]
|
||||
|
||||
[www.midnightwhispers.ca]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Queer as Folk
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -640,8 +805,14 @@ extratags:
|
||||
cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
|
||||
|
||||
[www.ncisfiction.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:NCIS
|
||||
|
||||
[www.phoenixsong.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extraships:Harry Potter/Ginny Weasley
|
||||
|
||||
## 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
|
||||
@@ -650,16 +821,43 @@ cover_exclusion_regexp:/stories/999/images/.*?_trophy.png
|
||||
#password:yourpassword
|
||||
|
||||
[www.potionsandsnitches.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
|
||||
[www.prisonbreakfic.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Prison Break
|
||||
|
||||
[www.scarvesandcoffee.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Glee
|
||||
extracharacters:Kurt Hummel,Blaine Anderson
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.sinful-desire.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Supernatural
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.siye.co.uk]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Harry Potter,Ginny Weasley
|
||||
extraships:Harry Potter/Ginny Weasley
|
||||
|
||||
[www.squidge.org/peja]
|
||||
# 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,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,description
|
||||
titlepage_entries: series,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.
|
||||
@@ -667,6 +865,16 @@ titlepage_entries: series,category,genre,language,characters,status,datePublishe
|
||||
titlepage_entries: series,category,genre,language,status,datePublished,dateUpdated,dateCreated,rating,warnings,numChapters,site,storyUrl, authorUrl, description
|
||||
|
||||
[www.storiesofarda.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Lord of the Rings
|
||||
|
||||
[www.thepetulantpoetess.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
## program can prompt you, or you can save it in config. In
|
||||
## commandline version, this should go in your personal.ini, not
|
||||
## defaults.ini.
|
||||
#username:YourName
|
||||
#password:yourpassword
|
||||
|
||||
[www.thewriterscoffeeshop.com]
|
||||
## Some sites require login (or login for some rated stories) The
|
||||
@@ -698,8 +906,13 @@ collect_series: false
|
||||
#password:yourpassword
|
||||
|
||||
[www.twilightarchives.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
[www.twilighted.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
## 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
|
||||
@@ -711,6 +924,9 @@ collect_series: false
|
||||
collect_series: false
|
||||
|
||||
[www.twiwrite.net]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Twilight
|
||||
|
||||
## 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
|
||||
@@ -722,6 +938,11 @@ collect_series: false
|
||||
collect_series: false
|
||||
|
||||
[www.walkingtheplank.org]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Harry Potter
|
||||
extracharacters:Severus Snape,Harry Potter
|
||||
extraships:Severus Snape/Harry Potter
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
@@ -742,7 +963,15 @@ collect_series: false
|
||||
## personal.ini, not defaults.ini.
|
||||
#is_adult:true
|
||||
|
||||
[www.wolverineandrogue.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:X-Men Movie
|
||||
extracharacters:Wolverine,Rogue
|
||||
|
||||
[www.wraithbait.com]
|
||||
## Site dedicated to these categories/characters/ships
|
||||
extracategories:Stargate: Atlantis
|
||||
|
||||
## Some sites do not require a login, but do require the user to
|
||||
## confirm they are adult for adult content. In commandline version,
|
||||
## this should go in your personal.ini, not defaults.ini.
|
||||
|
||||
+4
-14
@@ -1,10 +1,9 @@
|
||||
## This is an example of what your personal configuration might look
|
||||
## like.
|
||||
## like. Uncomment options by removing the '#' in front of them.
|
||||
|
||||
[defaults]
|
||||
## Some sites also require the user to confirm they are adult for
|
||||
## adult content. In commandline version, this should go in your
|
||||
## personal.ini, not defaults.ini.
|
||||
## adult content. Uncomment by removing '#' in front of is_adult.
|
||||
#is_adult:true
|
||||
|
||||
## include images from img tags in the body and summary of
|
||||
@@ -29,18 +28,9 @@
|
||||
## space.
|
||||
#grayscale_images: false
|
||||
|
||||
## Try to collect series name and number of this story in series.
|
||||
## Some sites (ab)use 'series' for reading lists and personal
|
||||
## collections. This lets us turn it on and off by site without
|
||||
## keeping a lengthy titlepage_entries per site and prevents it
|
||||
## updating in the plugin.
|
||||
## Turn off in [defaults] or [overrides] to prevent all sites from
|
||||
## updating series column.
|
||||
## default is true
|
||||
#collect_series: false
|
||||
|
||||
## Most common, I expect will be using this to save username/passwords
|
||||
## for different sites.
|
||||
## for different sites. Here are a few examples.
|
||||
|
||||
[www.twilighted.net]
|
||||
#username:YourPenname
|
||||
#password:YourPassword
|
||||
|
||||
Reference in New Issue
Block a user