Compare commits

..
89 Commits
Author SHA1 Message Date
Jim Miller f090485369 Show download count in Jobs list. 2016-02-14 10:18:03 -06:00
Jim Miller 2af3a44e11 Detected no-story for number, raise StoryDoesNotExist(SIYE). 2016-02-14 10:09:38 -06:00
Jim Miller 70d7253dac Fix some ini and highlighting issues. 2016-02-13 09:29:15 -06:00
Jim Miller be4e3610d5 Remove TtH authsoup debug dump. 2016-02-10 13:03:43 -06:00
Jim Miller 69507816a4 Allow Pairing w/o Centered category -> chars in TtH. 2016-02-10 12:46:56 -06:00
Jim Miller 20a789566f Add new AccessDenied exception for one line output in CLI. 2016-02-10 12:19:02 -06:00
Jim Miller 138de3ac3a Adding Incomplete status state to adapter_storiesonlinenet 2016-02-09 17:22:12 -06:00
Jim Miller f2960a8db4 Update translations 2016-02-09 10:35:29 -06:00
Jim Miller 3dd6550882 Fetch updated 'mi' from DB before generating covers. 2016-02-09 10:34:09 -06:00
Jim Miller 9f511dad8d Fix 'In Progress' to 'In-Progress' like all the others. 2016-02-09 10:25:43 -06:00
Jim Miller 7a08e3afdd Add automatic adding of unrecognized metadata in base_efiction. For tgstorytime.com. 2016-02-06 13:22:51 -06:00
Jim Miller a644beea94 Add (partial) translations for Estonian and Norwegian Bokmål 2016-02-05 11:50:27 -06:00
Jim Miller b275007393 Fix for portkey.org--Don't use cache on first hit in case added adult cookie. 2016-02-05 11:46:53 -06:00
Jim Miller 13602b023d Update translations. 2016-02-02 10:03:38 -06:00
Jim Miller 1be99aa95c Fix for replace_br_with_p(htmlheuristics) when author includes <>. 2016-02-02 09:58:53 -06:00
Jim Miller 4bf3399e35 Correct outdated ini comment re *_filename. 2016-02-01 14:24:01 -06:00
Jim Miller 35c066ea65 Add code for lazyload images in base_xenforoforum. 2016-02-01 14:23:13 -06:00
Jim Miller b25a869185 Fix fictionally.ord description so calibre doesn't <code> it. 2016-01-30 12:29:07 -06:00
Jim Miller 22e916bda9 Fix for html5lib handling noscript oddly, noticed with fictionalley.org. 2016-01-30 12:19:19 -06:00
Jim Miller 784375d15e Adding Word Count post-processing option, like Smarten Punct. 2016-01-29 22:34:07 -06:00
Jim Miller 18fd7d3653 Highlight color for *_format ini option 2016-01-26 14:14:41 -06:00
Jim Miller 34333a1c48 Change deprecated has_key() to has_attr() on BS objects. 2016-01-26 14:06:55 -06:00
Jim Miller 4069b1d15d Apply *_format ini option to date/time types. For calibre_* columns passed in. 2016-01-26 11:43:23 -06:00
Jim Miller b962059e4c Add byline site specific metadata for AO3. 2016-01-25 12:20:02 -06:00
Jim Miller 679fcc9d47 Fix for quotev.com change to story image. 2016-01-22 10:00:58 -06:00
Jim Miller d1a8b69f50 Update translations. 2016-01-18 16:23:46 -06:00
Jim Miller 1e66108506 Bump versions. 2016-01-18 16:20:34 -06:00
Jim Miller 5ec09f3936 Fix for TtH Story-42 matching Story-421 while searching auth pages. 2016-01-17 10:06:28 -06:00
Jim Miller 3f005cdfbd Update quotev.com adapter. 2016-01-14 13:45:33 -06:00
Jim Miller 17157498a9 Remove defunct sg1-heliopolis.com site after down for +1 month. 2016-01-11 09:40:52 -06:00
Jim Miller 191e13bfad Fix so ships_CHARS and sort_ships_splits work together. 2016-01-06 17:39:31 -06:00
Jim Miller 4bd288c3ac Update base_xenforoforum for SV threadmark change. 2015-12-31 10:02:37 -06:00
Jim Miller 95c2cbb77b Update translations. 2015-12-31 10:02:07 -06:00
Jim Miller 13e5615dea Skip (instead of error) if asked to UnNew non-FFF epubs. 2015-12-28 09:44:41 -06:00
Jim Miller 1553bd2fac Allow Anonymous author for quotev.com. 2015-12-27 17:27:18 -06:00
Jim Miller 4ac6c3dfdd Adding additional metadata collection, potionsandsnitches & walkingtheplankorg 2015-12-27 17:12:03 -06:00
Jim Miller 9a066be859 Fixes for problems with urlparse.parse_qs in adapters and new unique_list with authorId. Also BS4 fixes to adapter_spikeluvercom.py 2015-12-21 13:14:47 -06:00
Jim Miller 39bfc6632c Correct CLI version 2015-12-19 17:21:14 -06:00
Jim Miller 4a82818b59 Bump Versions 2015-12-19 17:08:44 -06:00
Jim Miller c059315bf8 Discard HTML comments in replace_br_with_p. 2015-12-18 15:55:43 -06:00
Jim Miller 2b623ed429 Add centeredcat_to_characters, pairingcat_to_characters_ships, romancecat_to_characters_ships features to TtH adapter. 2015-12-18 15:54:33 -06:00
Jim Miller 7986fcbb97 Fix for literotica. 2015-12-16 10:45:11 -06:00
Jim Miller 38d0a306ce Merge branch 'master' of https://github.com/JimmXinu/FanFicFare 2015-12-16 09:34:02 -06:00
Jim Miller 7ecf429ca9 Merge pull request #112 from user39189/mcstories-adapter
Add site adapter for mcstories.com
2015-12-16 09:34:00 -06:00
Jim Miller 1b57325135 Merge branch 'mcstories-adapter' of https://github.com/user39189/FanFicFare 2015-12-16 09:32:17 -06:00
user39189 a44afbd573 Match literotica adapter's behavior with story tags 2015-12-15 23:39:29 -05:00
user39189 60ff1986fa Add mcstories.com site adapter 2015-12-15 23:28:51 -05:00
Jim Miller 39439f75b6 Update translations 2015-12-15 11:59:02 -06:00
Jim Miller 50dabc1f09 Fix for yet another storiesonline.net site change. 2015-12-15 11:58:00 -06:00
Jim Miller b314c915d4 Add keep_html_attrs and replace_tags_with_spans features. 2015-12-13 10:42:42 -06:00
Jim Miller 8683c59d80 Update translation strings. 2015-12-13 10:41:55 -06:00
Jim Miller ab515644e4 Fix for replace_br_with_p with BS4. 2015-12-11 23:04:56 -06:00
Jim Miller f50a27a8b5 Change entities to actual utf8 chars in adapter_fanfictionjunkiesde.py. 2015-12-11 13:12:26 -06:00
Jim Miller 4931893bf0 Make metadata lists unique even when keep_in_order_. 2015-12-11 12:54:23 -06:00
Jim Miller 247b12aaa8 Remove some debug output. 2015-12-11 12:53:53 -06:00
Jim Miller bddd02ce1c Update html5lib from 0.9x5 to 0.9x7. 2015-12-11 10:28:14 -06:00
Jim Miller 7a42c84f51 Remove 'like' from XenForoForum chapter links in index post. 2015-12-11 10:27:45 -06:00
Jim Miller 40a1a99c05 Update adapters from BS3 to BS4, remove BS3. 2015-12-09 21:11:10 -06:00
Jim Miller 4a5dce3a65 Convert base_efiction_adapter to BS4. 2015-12-08 22:34:37 -06:00
Jim Miller dde2876d5d Convert base_efiction_adapter to BS4. 2015-12-08 13:00:34 -06:00
Jim Miller 1bb247e1ba Convert replace_br_with_p module to BS4. 2015-12-08 12:21:15 -06:00
Jim Miller 1446350747 Convert mobi.py's html.py module to BS4. 2015-12-08 12:10:47 -06:00
Jim Miller bc57c2cada Strip <script> tags from HTML. 2015-12-07 22:15:49 -06:00
Jim Miller 641865383e Suppress some debug output for the common failedtoload image case. 2015-12-07 22:15:21 -06:00
Jim Miller 18c8b0f6fa Add some additional BG log output. 2015-12-07 22:14:19 -06:00
Jim Miller 87721c9b64 Update to BeautifulSoup 4.4.1. 2015-12-07 14:04:57 -06:00
Jim Miller 15f2fc3c30 Add dblquotes around folder name on IMAP lookup. 2015-12-07 13:48:14 -06:00
Jim Miller e7a1560734 Improve basic chapter_title_strip_pattern, add warnings for same. 2015-12-06 20:10:32 -06:00
Jim Miller f220be69de Fix for hpfandom.net change so one-shot stories will work correctly. 2015-11-25 11:04:28 -06:00
Jim Miller 3ecd5ecc39 Set FimF adult cookie for 'get urls from page' 2015-11-25 10:50:14 -06:00
Jim Miller 2b2f923a83 Merge pull request #110 from de3sw2aq1/storiesonlinenet-https
storiesonline.net login requires https
This was also reported on mobileread.com, but you beat me to the commit, so we'll use your version. :-)
2015-11-24 10:35:21 -06:00
de3sw2aq1 b1c07344dd storiesonline.net login requires https
The old HTTP login page now returns a 404 error. Use HTTPS instead.
2015-11-24 05:58:05 +00:00
Jim Miller 6b28482514 Bump versions 2015-11-23 15:30:33 -06:00
Jim Miller b210007f59 Update translations 2015-11-23 15:30:18 -06:00
Jim Miller 08bc200c5c Updates for mediaminer.org. 2015-11-19 13:46:13 -06:00
Jim Miller 43ec9c0232 Remove some debug output. 2015-11-19 13:45:44 -06:00
Jim Miller a0c752c61f Make sort_ships honor doreplacements (and .NOREPL) flag. 2015-11-18 12:51:53 -06:00
Jim Miller ed84f10bfb Remove debug some output 2015-11-14 09:25:00 -06:00
Jim Miller 1d1e43f16f Change str() to unicode() many places. 2015-11-13 14:45:26 -06:00
Jim Miller ba4a29d646 Update translations. 2015-11-13 14:44:51 -06:00
Jim Miller 7ba523f20d Reduce over long error msgs when editing personal.ini(plugin). 2015-11-06 15:48:12 -06:00
Jim Miller 0e42f758ab Add sort_ships_splits and ships_CHARS. 2015-11-05 20:26:08 -06:00
Jim Miller e2995a7614 Add sort_ships_splits and ships_CHARS. 2015-11-05 13:56:07 -06:00
Jim Miller 3abb78a15e Fix to allow 'Update Calibre Metadata Only(Saved Column)' as default. 2015-11-04 10:58:47 -06:00
Jim Miller 3b9b9c1eb7 Fix for UnNew epubs with & or double quote in chap title. 2015-10-31 14:27:25 -05:00
Jim Miller 235c5ae62f Fix for whofic.com with replace_br_with_p 2015-10-31 14:26:51 -05:00
Jim Miller b2238e10e7 Add html5lib to more BS4 calls, convert another place to BS4. 2015-10-22 18:16:30 -05:00
Jim Miller a5bc7bd757 Comment out debug that can cause additional errors depending on content. 2015-10-22 18:15:42 -05:00
Jim Miller 36d848fde9 Add series00-04HTml to defaults.ini for AO3. 2015-10-21 18:43:16 -05:00
156 changed files with 13531 additions and 8258 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import sys
@@ -42,7 +42,7 @@ class FanFicFareBase(InterfaceActionBase):
description = _('UI plugin to download FanFiction stories from various sites.')
supported_platforms = ['windows', 'osx', 'linux']
author = 'Jim Miller'
version = (2, 2, 14)
version = (2, 2, 17)
minimum_calibre_version = (1, 48, 0)
#: This field defines the GUI plugin class that contains all the code
+44 -18
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import logging
@@ -81,8 +81,8 @@ no_trans = { 'pini':'personal.ini',
STD_COLS_SKIP = ['size','cover','news','ondevice','path','series_sort','sort']
from calibre_plugins.fanficfare_plugin.prefs \
import (prefs, PREFS_NAMESPACE, updatecalcover_order, calcover_save_options,
gencalcover_order, SAVE_YES, SAVE_NO)
import (prefs, PREFS_NAMESPACE, prefs_save_options, updatecalcover_order,
gencalcover_order, do_wordcount_order, SAVE_YES, SAVE_NO)
from calibre_plugins.fanficfare_plugin.dialogs \
import (UPDATE, UPDATEALWAYS, collision_order, save_collisions, RejectListDialog,
@@ -261,6 +261,7 @@ class ConfigWidget(QWidget):
prefs['checkforurlchange'] = self.basic_tab.checkforurlchange.isChecked()
prefs['injectseries'] = self.basic_tab.injectseries.isChecked()
prefs['matchtitleauth'] = self.basic_tab.matchtitleauth.isChecked()
prefs['do_wordcount'] = prefs_save_options[unicode(self.basic_tab.do_wordcount.currentText())]
prefs['smarten_punctuation'] = self.basic_tab.smarten_punctuation.isChecked()
prefs['reject_always'] = self.basic_tab.reject_always.isChecked()
@@ -286,10 +287,10 @@ class ConfigWidget(QWidget):
prefs['cal_cols_pass_in'] = self.personalini_tab.cal_cols_pass_in.isChecked()
# Covers tab
prefs['updatecalcover'] = calcover_save_options[unicode(self.calibrecover_tab.updatecalcover.currentText())]
prefs['updatecalcover'] = prefs_save_options[unicode(self.calibrecover_tab.updatecalcover.currentText())]
# for backward compatibility:
prefs['updatecover'] = prefs['updatecalcover'] == SAVE_YES
prefs['gencalcover'] = calcover_save_options[unicode(self.calibrecover_tab.gencalcover.currentText())]
prefs['gencalcover'] = prefs_save_options[unicode(self.calibrecover_tab.gencalcover.currentText())]
prefs['calibre_gen_cover'] = self.calibrecover_tab.calibre_gen_cover.isChecked()
prefs['plugin_gen_cover'] = self.calibrecover_tab.plugin_gen_cover.isChecked()
prefs['gcnewonly'] = self.calibrecover_tab.gcnewonly.isChecked()
@@ -478,6 +479,10 @@ class BasicTab(QWidget):
self.lookforurlinhtml.setChecked(prefs['lookforurlinhtml'])
self.l.addWidget(self.lookforurlinhtml)
proc_gb = groupbox = QGroupBox(_("Post Processing Options"))
self.l = QVBoxLayout()
groupbox.setLayout(self.l)
self.mark = QCheckBox(_("Mark added/updated books when finished?"),self)
self.mark.setToolTip(_("Mark added/updated books when finished. Use with option below.\nYou can also manually search for 'marked:fff_success'.\n'marked:fff_failed' is also available, or search 'marked:fff' for both."))
self.mark.setChecked(prefs['mark'])
@@ -493,6 +498,24 @@ class BasicTab(QWidget):
self.smarten_punctuation.setChecked(prefs['smarten_punctuation'])
self.l.addWidget(self.smarten_punctuation)
tooltip = _("Calculate Word Counts using Calibre internal methods.\n"
"Many sites include Word Count, but many do not.\n"
"This will count the words in each book and include it as if it came from the site.")
horz = QHBoxLayout()
label = QLabel(_('Calculate Word Count:'))
label.setToolTip(tooltip)
horz.addWidget(label)
self.do_wordcount = QComboBox(self)
for i in do_wordcount_order:
self.do_wordcount.addItem(i)
self.do_wordcount.setCurrentIndex(self.do_wordcount.findText(prefs_save_options[prefs['do_wordcount']]))
self.do_wordcount.setToolTip(tooltip)
label.setBuddy(self.do_wordcount)
horz.addWidget(self.do_wordcount)
self.l.addLayout(horz)
self.autoconvert = QCheckBox(_("Automatically Convert new/update books?"),self)
self.autoconvert.setToolTip(_("Automatically call calibre's Convert for new/update books.\nConverts to the current output format as chosen in calibre's\nPreferences->Behavior settings."))
self.autoconvert.setChecked(prefs['autoconvert'])
@@ -564,14 +587,17 @@ class BasicTab(QWidget):
horz = QHBoxLayout()
horz.addWidget(cali_gb)
vertleft = QVBoxLayout()
vertleft.addWidget(cali_gb)
vertleft.addWidget(proc_gb)
vert = QVBoxLayout()
vert.addWidget(gui_gb)
vert.addWidget(misc_gb)
vert.addWidget(rej_gb)
vertright = QVBoxLayout()
vertright.addWidget(gui_gb)
vertright.addWidget(misc_gb)
vertright.addWidget(rej_gb)
horz.addLayout(vert)
horz.addLayout(vertleft)
horz.addLayout(vertright)
topl.addLayout(horz)
topl.insertStretch(-1)
@@ -840,11 +866,11 @@ class CalibreCoverTab(QWidget):
self.updatecalcover.addItem(i)
# back compat. If has own value, use.
if prefs['updatecalcover']:
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(calcover_save_options[prefs['updatecalcover']]))
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(prefs_save_options[prefs['updatecalcover']]))
elif prefs['updatecover']: # doesn't have own val, set YES if old value set.
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(calcover_save_options[SAVE_YES]))
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(prefs_save_options[SAVE_YES]))
else: # doesn't have own value, old value not set, NO.
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(calcover_save_options[SAVE_NO]))
self.updatecalcover.setCurrentIndex(self.updatecalcover.findText(prefs_save_options[SAVE_NO]))
self.updatecalcover.setToolTip(tooltip)
label.setBuddy(self.updatecalcover)
horz.addWidget(self.updatecalcover)
@@ -862,11 +888,11 @@ class CalibreCoverTab(QWidget):
self.gencalcover.addItem(i)
# back compat. If has own value, use.
# if prefs['gencalcover']:
self.gencalcover.setCurrentIndex(self.gencalcover.findText(calcover_save_options[prefs['gencalcover']]))
self.gencalcover.setCurrentIndex(self.gencalcover.findText(prefs_save_options[prefs['gencalcover']]))
# elif prefs['gencover']: # doesn't have own val, set YES if old value set.
# self.gencalcover.setCurrentIndex(self.gencalcover.findText(calcover_save_options[SAVE_YES]))
# self.gencalcover.setCurrentIndex(self.gencalcover.findText(prefs_save_options[SAVE_YES]))
# else: # doesn't have own value, old value not set, NO.
# self.gencalcover.setCurrentIndex(self.gencalcover.findText(calcover_save_options[SAVE_NO]))
# self.gencalcover.setCurrentIndex(self.gencalcover.findText(prefs_save_options[SAVE_NO]))
self.gencalcover.setToolTip(tooltip)
label.setBuddy(self.gencalcover)
@@ -990,7 +1016,7 @@ class CalibreCoverTab(QWidget):
## First, cover gen on/off
for e in self.gencov_elements:
e.setEnabled(calcover_save_options[unicode(self.gencalcover.currentText())] != SAVE_NO)
e.setEnabled(prefs_save_options[unicode(self.gencalcover.currentText())] != SAVE_NO)
# next, disable plugin settings when using calibre gen cov.
if not self.plugin_gen_cover.isChecked():
+32 -52
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import traceback, re
@@ -73,54 +73,30 @@ from calibre_plugins.fanficfare_plugin.fanficfare.configurable \
from inihighlighter import IniHighlighter
SKIP=_('Skip')
ADDNEW=_('Add New Book')
UPDATE=_('Update EPUB if New Chapters')
UPDATEALWAYS=_('Update EPUB Always')
OVERWRITE=_('Overwrite if Newer')
OVERWRITEALWAYS=_('Overwrite Always')
CALIBREONLY=_('Update Calibre Metadata from Web Site')
CALIBREONLYSAVECOL=_('Update Calibre Metadata from Saved Metadata Column')
collision_order=[SKIP,
ADDNEW,
UPDATE,
UPDATEALWAYS,
OVERWRITE,
OVERWRITEALWAYS,
CALIBREONLY,
CALIBREONLYSAVECOL,]
# best idea I've had for how to deal with config/pref saving the
# collision name in english.
SAVE_SKIP='Skip'
SAVE_ADDNEW='Add New Book'
SAVE_UPDATE='Update EPUB if New Chapters'
SAVE_UPDATEALWAYS='Update EPUB Always'
SAVE_OVERWRITE='Overwrite if Newer'
SAVE_OVERWRITEALWAYS='Overwrite Always'
SAVE_CALIBREONLY='Update Calibre Metadata Only'
SAVE_CALIBREONLYSAVECOL='Update Calibre Metadata Only(Saved Column)'
save_collisions={
SKIP:SAVE_SKIP,
ADDNEW:SAVE_ADDNEW,
UPDATE:SAVE_UPDATE,
UPDATEALWAYS:SAVE_UPDATEALWAYS,
OVERWRITE:SAVE_OVERWRITE,
OVERWRITEALWAYS:SAVE_OVERWRITEALWAYS,
CALIBREONLY:SAVE_CALIBREONLY,
SAVE_SKIP:SKIP,
SAVE_ADDNEW:ADDNEW,
SAVE_UPDATE:UPDATE,
SAVE_UPDATEALWAYS:UPDATEALWAYS,
SAVE_OVERWRITE:OVERWRITE,
SAVE_OVERWRITEALWAYS:OVERWRITEALWAYS,
SAVE_CALIBREONLY:CALIBREONLY,
SAVE_CALIBREONLYSAVECOL:CALIBREONLYSAVECOL,
}
anthology_collision_order=[UPDATE,
UPDATEALWAYS,
OVERWRITEALWAYS]
## moved to prefs.py so they can be included in jobs.py.
from calibre_plugins.fanficfare_plugin.prefs import \
( SAVE_YES,
SAVE_YES_UNLESS_SITE,
SKIP,
ADDNEW,
UPDATE,
UPDATEALWAYS,
OVERWRITE,
OVERWRITEALWAYS,
CALIBREONLY,
CALIBREONLYSAVECOL,
collision_order,
SAVE_SKIP,
SAVE_ADDNEW,
SAVE_UPDATE,
SAVE_UPDATEALWAYS,
SAVE_OVERWRITE,
SAVE_OVERWRITEALWAYS,
SAVE_CALIBREONLY,
SAVE_CALIBREONLYSAVECOL,
save_collisions,
anthology_collision_order,
)
gpstyle='QGroupBox {border:0; padding-top:10px; padding-bottom:0px; margin-bottom:0px;}' # background-color:red;
@@ -472,8 +448,9 @@ class AddNewDialog(SizePersistedDialog):
'updatemeta': self.updatemeta.isChecked(),
'bgmeta': False, # self.bgmeta.isChecked(),
'updateepubcover': self.updateepubcover.isChecked(),
'smarten_punctuation':self.prefs['smarten_punctuation']
}
'smarten_punctuation':self.prefs['smarten_punctuation'],
'do_wordcount':self.prefs['do_wordcount'],
}
if self.merge:
retval['fileform']=='epub'
@@ -897,7 +874,8 @@ class UpdateExistingDialog(SizePersistedDialog):
'updatemeta': self.updatemeta.isChecked(),
'bgmeta': self.bgmeta.isChecked(),
'updateepubcover': self.updateepubcover.isChecked(),
'smarten_punctuation':self.prefs['smarten_punctuation']
'smarten_punctuation':self.prefs['smarten_punctuation'],
'do_wordcount':self.prefs['do_wordcount'],
}
class StoryListTableWidget(QTableWidget):
@@ -1456,6 +1434,8 @@ class ViewLog(SizePersistedDialog):
## error = (lineno, msg)
for (lineno, error_msg) in errors:
# print('adding label for error:%s: %s'%(lineno, error_msg))
if len(error_msg) > 200:
error_msg=error_msg[:200]+" ..."
label = QLabel('%s: %s'%(lineno, error_msg))
label.setWordWrap(True)
label.setStyleSheet("QLabel { margin-left: 2em; color : blue; } QLabel:hover { color: red; }");
+21 -9
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import logging
@@ -476,7 +476,8 @@ class FanFicFarePlugin(InterfaceAction):
'updatemeta': prefs['updatemeta'],
'bgmeta': False,
'updateepubcover': prefs['updateepubcover'],
'smarten_punctuation':prefs['smarten_punctuation']
'smarten_punctuation':prefs['smarten_punctuation'],
'do_wordcount':prefs['do_wordcount'],
},"\n".join(url_list))
else:
self.gui.status_bar.show_message(_('Finished Fetching Story URLs from Email.'),3000)
@@ -528,7 +529,8 @@ class FanFicFarePlugin(InterfaceAction):
def get_urls_from_page(self,url):
logger.debug("get_urls_from_page URL:%s"%url)
if 'archiveofourown.org' in url:
## some sites hide mature links unless logged in.
if 'archiveofourown.org' in url or 'fimfiction.net' in url:
configuration = get_fff_config(url)
else:
configuration = None
@@ -607,8 +609,9 @@ class FanFicFarePlugin(InterfaceAction):
status_prefix=_("Books UnNewed"))
def get_unnew_books_loop(self,book,db=None,tdir=None):
if book['calibre_id'] and db.has_format(book['calibre_id'],'EPUB',index_is_id=True):
book['changed']=False
if book['calibre_id'] and db.has_format(book['calibre_id'],'EPUB',index_is_id=True) and self.get_story_url(db,book['calibre_id']):
tmp = PersistentTemporaryFile(prefix='%s-'%book['calibre_id'],
suffix='.epub',
dir=tdir)
@@ -1305,11 +1308,14 @@ class FanFicFarePlugin(InterfaceAction):
if collision == OVERWRITE and \
db.has_format(book_id,formmapping[fileform],index_is_id=True):
logger.debug("OVERWRITE file: "+db.format_abspath(book_id, formmapping[fileform], index_is_id=True))
fileupdated=datetime.fromtimestamp(os.stat(db.format_abspath(book_id, formmapping[fileform], index_is_id=True))[8])
book['fileupdated']=fileupdated
logger.debug("OVERWRITE file updated: %s"%fileupdated)
book['updated']=fileupdated
if not bgmeta:
# check make sure incoming is newer.
lastupdated=story.getMetadataRaw('dateUpdated')
logger.debug("OVERWRITE site updated: %s"%lastupdated)
# updated doesn't have time (or is midnight), use dates only.
# updated does have time, use full timestamps.
@@ -1470,7 +1476,7 @@ class FanFicFarePlugin(InterfaceAction):
cpus = self.gui.job_manager.server.pool_size
args = ['calibre_plugins.fanficfare_plugin.jobs', 'do_download_worker',
(book_list, options, cpus, merge)]
desc = _('Download FanFiction Book')
desc = _('Download %s FanFiction Book(s)') % len(filter(lambda x : x['good'], book_list))
job = self.gui.job_manager.run_job(
self.Dispatcher(partial(self.download_list_completed,options=options,merge=merge)),
func, args=args,
@@ -2022,8 +2028,11 @@ class FanFicFarePlugin(InterfaceAction):
cover_generated = False # flag for polish below.
# Yes, should do gencov. Which?
if prefs['calibre_gen_cover'] and HAS_CALGC:
# calibre's builtin, if available.
cdata = cal_generate_cover(mi)
## calibre's builtin, if available. fetch updated mi
## object from database. Additional normalization of
## series (at least) happens
realmi = db.get_metadata(book_id, index_is_id=True)
cdata = cal_generate_cover(realmi)
db.set_cover(book_id, cdata)
cover_generated = True
elif prefs['plugin_gen_cover'] and 'Generate Cover' in self.gui.iactions:
@@ -2070,6 +2079,9 @@ class FanFicFarePlugin(InterfaceAction):
if setting_name:
logger.debug("Running Generate Cover with settings %s."%setting_name)
## fetch updated mi object from
## database. Additional normalization of series
## (at least) happens
realmi = db.get_metadata(book_id, index_is_id=True)
gc_plugin.generate_cover_for_book(realmi,saved_setting_name=setting_name)
cover_generated = True
+27 -8
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller, 2011, Grant Drake <grant.drake@gmail.com>'
__copyright__ = '2016, Jim Miller, 2011, Grant Drake <grant.drake@gmail.com>'
__docformat__ = 'restructuredtext en'
import logging
@@ -20,6 +20,9 @@ from calibre.constants import numeric_version as calibre_version
from calibre.utils.date import local_tz
from calibre.library.comments import sanitize_comments_html
from calibre_plugins.fanficfare_plugin.wordcount import get_word_count
from calibre_plugins.fanficfare_plugin.prefs import (SAVE_YES, SAVE_YES_UNLESS_SITE)
# ------------------------------------------------------------------------------
#
# Functions to perform downloads using worker jobs
@@ -85,10 +88,16 @@ def do_download_worker(book_list,
logger.info(job.details)
if count >= total:
logger.info("\n"+_("Successful:")+"\n%s\n"%("\n".join([book['url'] for book in
filter(lambda x: x['good'], book_list) ] ) ) )
logger.info("\n"+_("Unsuccessful:")+"\n%s\n"%("\n".join([book['url'] for book in
filter(lambda x: not x['good'], book_list) ] ) ) )
## ordering first by good vs bad, then by listorder.
good_list = filter(lambda x : x['good'], book_list)
bad_list = filter(lambda x : not x['good'], book_list)
good_list = sorted(good_list,key=lambda x : x['listorder'])
bad_list = sorted(bad_list,key=lambda x : x['listorder'])
logger.info("\n"+_("Download Results:")+"\n%s\n"%("\n".join([ "%(url)s %(comment)s" % book for book in good_list+bad_list])))
logger.info("\n"+_("Successful:")+"\n%s\n"%("\n".join([book['url'] for book in good_list])))
logger.info("\n"+_("Unsuccessful:")+"\n%s\n"%("\n".join([book['url'] for book in bad_list])))
break
server.close()
@@ -142,7 +151,7 @@ def do_download_for_worker(book,options,merge,notification=lambda x,y:x):
adapter.setChaptersRange(book['begin'],book['end'])
adapter.load_cookiejar(options['cookiejarfile'])
logger.debug("cookiejar:%s"%adapter.cookiejar)
#logger.debug("cookiejar:%s"%adapter.cookiejar)
adapter.set_pagecache(options['pagecache'])
story = adapter.getStoryMetadataOnly()
@@ -211,6 +220,7 @@ def do_download_for_worker(book,options,merge,notification=lambda x,y:x):
logger.info("write to %s"%outfile)
inject_cal_cols(book,story,configuration)
writer.writeStory(outfilename=outfile, forceOverwrite=True)
book['comment'] = 'Download %s completed, %s chapters.'%(options['fileform'],story.getMetadata("numChapters"))
book['all_metadata'] = story.getAllMetadata(removeallentities=True)
if options['savemetacol'] != '':
@@ -265,6 +275,16 @@ def do_download_for_worker(book,options,merge,notification=lambda x,y:x):
if options['savemetacol'] != '':
book['savemetacol'] = story.dump_html_metadata()
if options['do_wordcount'] == SAVE_YES or (
options['do_wordcount'] == SAVE_YES_UNLESS_SITE and not story.getMetadataRaw('numWords') ):
wordcount = get_word_count(outfile)
logger.info("get_word_count:%s"%wordcount)
story.setMetadata('numWords',wordcount)
writer.writeStory(outfilename=outfile, forceOverwrite=True)
book['all_metadata'] = story.getAllMetadata(removeallentities=True)
if options['savemetacol'] != '':
book['savemetacol'] = story.dump_html_metadata()
if options['smarten_punctuation'] and options['fileform'] == "epub" \
and calibre_version >= (0, 9, 39):
# for smarten punc
@@ -280,8 +300,7 @@ def do_download_for_worker(book,options,merge,notification=lambda x,y:x):
opts = O(**opts)
log = Log(level=Log.DEBUG)
# report = []
polish({outfile:outfile}, opts, log, logger.info) # report.append
polish({outfile:outfile}, opts, log, logger.info)
except NotGoingToDownload as d:
book['good']=False
+124 -15
View File
@@ -266,12 +266,32 @@ keep_summary_html:true
## If set true, any style attributes on tags in the story HTML will be
## kept. Useful for keeping extra colors & formatting from original.
## DEPRECATED - use keep_html_attrs (or add_to_keep_html_attrs) instead.
#keep_style_attr: false
## If set true, any title attributes on tags in the story HTML will be
## kept. Some sites(AO3) use <a title="Translation">Foreign language</a>
## DEPRECATED - use keep_html_attrs (or add_to_keep_html_attrs) instead.
#keep_title_attr: false
## Some attributes cause problems for EBook readers. By default,
## FanFicFare will remove all attributes except the ones specified
## from all tags. (The only exception is that <img> tags will also
## keep src, alt and longdesc attributes.)
## Example: To add 'style', 'title' and 'align' to the list to keep,
## in your personal.ini [defaults] put:
## add_to_keep_html_attrs:,style,title,align
keep_html_attrs:href,name,class,id
## Tags listed here will be replaced with <span class="tagname">.
## For example: <u>underlined text</u> becomes
## <span class="u">underlined text</span>
## Note that the output_css should contain the class .u, .big, etc for
## the spans to be useful.
## This feature is for replacing old tags deprecated/removed in newer
## HTML and EPUB standards.
replace_tags_with_spans:u,big,small
## If a chapter range was given, use this pattern for the book title.
## replace_metadata and include/exclude will be applied *after* this.
## Set to empty value to disable.
@@ -299,14 +319,21 @@ add_chapter_numbers:false
## "1: Start" => "Start"
## "2, Chapter the second" => "Chapter the second"
## etc
chapter_title_strip_pattern:^[0-9]+[\.: -]+
## Leaves unchanged "1.1" or "1"
## Note that your ToC entry(ies) may be empty if the
## chapter_title_strip_pattern removes everything and you don't have
## add_chapter_numbers:true.
chapter_title_strip_pattern:^[0-9]+[\.: -]+(?=[^0-9]|$)
## This version will strip all of the above *plus* remove 'Chapter 1':
## This version remove leading numbers and 'Chapter 1':
## "Chapter 1" => ""
## "1. Chapter 1" => ""
## "1. Chapter 1, Bob's First Clue" => "Bob's First Clue"
## "Chapter 2 - Pirates Place" => "Pirates Place"
## etc
## Note that your ToC entry(ies) may be empty if the
## chapter_title_strip_pattern removes everything and you don't have
## add_chapter_numbers:true.
#chapter_title_strip_pattern:^([0-9]+[\.: -]+)?(Chapter *[0-9]+[\.:, -]*)?
## If true, when updating an epub that already has old chapters, new
@@ -344,12 +371,19 @@ anthology_title_pattern:${title} Anthology
## any anthology tags.
anthology_tags:Anthology
## Reorder ships so b/a and c/b/a become a/b and a/b/c. Only separates
## on '/', so use replace_metadata to change separator first if
## needed. Something like: ships=>[ ]*(/|&amp;|&)[ ]*=>/ You can use
## ships_LIST to change the / back to something else if you want.
## Reorder ships so b/a and c/b/a become a/b and a/b/c. '/' is no
## longer hard coded and can be changed and added to with
## sort_ships_splits.
sort_ships:false
## Each line indicates first a regex that should be used to split each
## ships entry and then, after => the string to use to merge the parts
## back together. \s == blank space.
## Each part will have replace_metadata with key ships_CHARS applied.
sort_ships_splits:
[ ]*/[ ]*=>/
[ ]*&amp;[ ]*=>\s&amp;\s
## join_string_<entry> options -- FanFicFare list entries are comma
## separated by default. You can use this to change that. For example,
## if you want authors separated with ' & ' instead, use
@@ -491,6 +525,8 @@ output_css:
.smcap {font-variant: small-caps;}
.u {text-decoration: underline;}
.bold {font-weight: bold;}
.big { font-size: larger; }
.small { font-size: smaller; }
[txt]
## Add URLs since there aren't links.
@@ -571,6 +607,8 @@ output_css:
.smcap {font-variant: small-caps;}
.u {text-decoration: underline;}
.bold {font-weight: bold;}
.big { font-size: larger; }
.small { font-size: smaller; }
## include images from img tags in the body and summary of
## stories. Images will be converted to jpg for size if possible.
@@ -709,7 +747,7 @@ extratags: FanFiction,Testing,HTML
## AO3 adapter defines a few extra metadata entries.
## If there's ever more than 4 series, add series04,series04Url etc.
extra_valid_entries:fandoms,freeformtags,freefromtags,ao3categories,comments,kudos,hits,bookmarks,collections,series00,series01,series02,series03,series00Url,series01Url,series02Url,series03Url
extra_valid_entries:fandoms,freeformtags,freefromtags,ao3categories,comments,kudos,hits,bookmarks,collections,byline,series00,series01,series02,series03,series00Url,series01Url,series02Url,series03Url,series00HTML,series01HTML,series02HTML,series03HTML
fandoms_label:Fandoms
freeformtags_label:Freeform Tags
freefromtags_label:Freeform Tags
@@ -719,6 +757,14 @@ kudos_label:Kudos
hits_label:Hits
collections_label:Collections
bookmarks_label:Bookmarks
series00HTML_label:Series
series01HTML_label:Additional Series
series02HTML_label:Additional Series
series03HTML_label:Additional Series
## Assume entryUrl, apply to "<a class='%slink' href='%s'>%s</a>" to
## make entryHTML.
make_linkhtml_entries:series00,series01,series02,series03
## AO3 doesn't have anything it calls 'genre'. The adapter used to be
## hardcoded to include the site specific metadata freeformtags &
@@ -737,7 +783,7 @@ include_in_category:fandoms
include_in_freefromtags:freeformtags
## adds to titlepage_entries instead of replacing it.
#extra_titlepage_entries: fandoms,freeformtags,ao3categories,comments,kudos,hits,bookmarks,series00,series01,series02,series03,series00Url,series01Url,series02Url,series03Url
#extra_titlepage_entries: fandoms,freeformtags,ao3categories,comments,kudos,hits,bookmarks,series01HTML,series02HTML,series03HTML,byline
## adds to include_subject_tags instead of replacing it.
#extra_subject_tags:fandoms,freeformtags,ao3categories
@@ -1254,6 +1300,19 @@ extracategories:Lord of the Rings
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[mcstories.com]
## 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
## Clear FanFiction from defaults, site is original fiction.
extratags:Erotica
extra_valid_entries:eroticatags
eroticatags_label:Erotica Tags
extra_titlepage_entries: eroticatags
[merlinfic.dtwins.co.uk]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -1421,12 +1480,6 @@ extraships:Sam/Dean
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
[sg1-heliopolis.com]
## 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
[sheppardweir.com]
## Some sites require login (or login for some rated stories) The
## program can prompt you, or you can save it in config. In
@@ -1504,6 +1557,24 @@ extracategories:Transgender
## confirm they are adult for adult content.
#is_adult:true
## This site has a number of additional site specific metadata
## entries. This is the first test case of base_efiction 'Auto
## metadata' automatically including unrecognized metadata. Still
## requires entries in extra_valid_entries to be used.
extra_valid_entries:turnedinto,featureditems,locale,motivationforchange,sexualorientation,storytheme,bodymodification,personality,storytype,typeofchange
#add_to_titlepage_entries:,turnedinto,featureditems,locale,motivationforchange,sexualorientation,storytheme,bodymodification,personality,storytype,typeofchange
turnedinto_label:Turned Into
featureditems_label:Featured Items
locale_label:Locale
motivationforchange_label:Motivation for Change
sexualorientation_label:Sexual Orientation
storytheme_label:Story Theme
bodymodification_label:Body Modification
personality_label:Personality
storytype_label:Story Type
typeofchange_label:Type of Change
[thehexfiles.net]
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
@@ -1943,9 +2014,18 @@ extracategories:Harry Potter
extraships:Harry Potter/Ginny Weasley
[www.potionsandsnitches.org]
extra_valid_entries:stars,reviews,reads,takesplaces,snapeflavours,sitetags
stars_label:Frogs
takesplaces_label:Takes Place
snapeflavours_label:Snape Flavour
sitetags_label:Site Tags
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
add_to_include_subject_tags:,takesplaces,snapeflavours,sitetags
#add_to_extra_titlepage_entries:,stars,reviews,reads,takesplaces,snapeflavours,sitetags
[www.potterfics.com]
## Site dedicated to these categories/characters/ships
extracategories:Harry Potter
@@ -1969,11 +2049,15 @@ extracategories:Psych
extracategories:Queer as Folk
[quotev.com]
extra_valid_entries:pages,readers,reads,favorites
extra_valid_entries:pages,readers,reads,favorites,searchtags,comments
pages_label:Pages
readers_label:Readers
reads_label:Reads
favorites_label:Favorites
searchtags_label:Search Tags
comments_label:Comments
include_in_category:category,searchtags
[www.restrictedsection.org]
## Some sites require login (or login for some rated stories) The
@@ -2070,6 +2154,28 @@ collect_series: false
#username:YourName
#password:yourpassword
## TtH uses 'category' Buffy-Centered, Xander-Centered, etc. If
## centeredcat_to_characters:true, they will not be included in the
## category list and Buffy, Xander, etc will instead be included in
## the characters list.
centeredcat_to_characters:true
## TtH uses 'category' Pairing: Dawn, Pairing: Willow, etc, but only
## if there's already been a -Centered category, like Xander-Centered.
## If pairingcat_to_characters_ships:true, Pairing: Whomever will not
## be included in the category list and Dawn, Willow, etc will instead
## be included in the characters list *and* an entry added to the
## ships list for Centered character / Pairing character. IE,
## category: Xander-Centered, Pairing: Dawn becomes characters:
## Xander, Dawn and ships: Xander/Dawn.
pairingcat_to_characters_ships:true
## TtH uses 'category' Romance followed by a pairing 'category', such
## as Romance > Buffy/Spike. If romancecat_to_characters_ships:true,
## Romance will still be added to category, but Buffy/Spike will
## instead be added to characters Buffy, Spike and ships Buffy/Spike
romancecat_to_characters_ships:true
[www.twilightarchives.com]
## Site dedicated to these categories/characters/ships
extracategories:Twilight
@@ -2089,6 +2195,9 @@ extracategories:Twilight
collect_series: false
[www.walkingtheplank.org]
extra_valid_entries:reads
reads_label:Read Count
## 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.
+59 -3
View File
@@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2015, Jim Miller'
__copyright__ = '2016, Jim Miller'
__docformat__ = 'restructuredtext en'
import logging
@@ -15,9 +15,59 @@ import copy
from calibre.utils.config import JSONConfig
from calibre.gui2.ui import get_gui
from calibre_plugins.fanficfare_plugin.dialogs import SAVE_UPDATE
from calibre_plugins.fanficfare_plugin.common_utils import get_library_uuid
SKIP=_('Skip')
ADDNEW=_('Add New Book')
UPDATE=_('Update EPUB if New Chapters')
UPDATEALWAYS=_('Update EPUB Always')
OVERWRITE=_('Overwrite if Newer')
OVERWRITEALWAYS=_('Overwrite Always')
CALIBREONLY=_('Update Calibre Metadata from Web Site')
CALIBREONLYSAVECOL=_('Update Calibre Metadata from Saved Metadata Column')
collision_order=[SKIP,
ADDNEW,
UPDATE,
UPDATEALWAYS,
OVERWRITE,
OVERWRITEALWAYS,
CALIBREONLY,
CALIBREONLYSAVECOL,]
# best idea I've had for how to deal with config/pref saving the
# collision name in english.
SAVE_SKIP='Skip'
SAVE_ADDNEW='Add New Book'
SAVE_UPDATE='Update EPUB if New Chapters'
SAVE_UPDATEALWAYS='Update EPUB Always'
SAVE_OVERWRITE='Overwrite if Newer'
SAVE_OVERWRITEALWAYS='Overwrite Always'
SAVE_CALIBREONLY='Update Calibre Metadata Only'
SAVE_CALIBREONLYSAVECOL='Update Calibre Metadata Only(Saved Column)'
save_collisions={
SKIP:SAVE_SKIP,
ADDNEW:SAVE_ADDNEW,
UPDATE:SAVE_UPDATE,
UPDATEALWAYS:SAVE_UPDATEALWAYS,
OVERWRITE:SAVE_OVERWRITE,
OVERWRITEALWAYS:SAVE_OVERWRITEALWAYS,
CALIBREONLY:SAVE_CALIBREONLY,
CALIBREONLYSAVECOL:SAVE_CALIBREONLYSAVECOL,
SAVE_SKIP:SKIP,
SAVE_ADDNEW:ADDNEW,
SAVE_UPDATE:UPDATE,
SAVE_UPDATEALWAYS:UPDATEALWAYS,
SAVE_OVERWRITE:OVERWRITE,
SAVE_OVERWRITEALWAYS:OVERWRITEALWAYS,
SAVE_CALIBREONLY:CALIBREONLY,
SAVE_CALIBREONLYSAVECOL:CALIBREONLYSAVECOL,
}
anthology_collision_order=[UPDATE,
UPDATEALWAYS,
OVERWRITEALWAYS]
# Show translated strings, but save the same string in prefs so your
# prefs are the same in different languages.
YES=_('Yes, Always')
@@ -26,9 +76,11 @@ YES_IF_IMG=_('Yes, if EPUB has a cover image')
SAVE_YES_IF_IMG='Yes, if img'
YES_UNLESS_IMG=_('Yes, unless FanFicFare found a cover image')
SAVE_YES_UNLESS_IMG='Yes, unless img'
YES_UNLESS_SITE=_('Yes, unless found on site')
SAVE_YES_UNLESS_SITE='Yes, unless site'
NO=_('No')
SAVE_NO='No'
calcover_save_options = {
prefs_save_options = {
YES:SAVE_YES,
SAVE_YES:YES,
YES_IF_IMG:SAVE_YES_IF_IMG,
@@ -37,9 +89,12 @@ calcover_save_options = {
SAVE_YES_UNLESS_IMG:YES_UNLESS_IMG,
NO:SAVE_NO,
SAVE_NO:NO,
YES_UNLESS_SITE:SAVE_YES_UNLESS_SITE,
SAVE_YES_UNLESS_SITE:YES_UNLESS_SITE,
}
updatecalcover_order=[YES,YES_IF_IMG,NO]
gencalcover_order=[YES,YES_UNLESS_IMG,NO]
do_wordcount_order=[YES,YES_UNLESS_SITE,NO]
# if don't have any settings for FanFicFarePlugin, copy from
# predecessor FanFictionDownLoaderPlugin.
@@ -78,6 +133,7 @@ default_prefs['checkforseriesurlid'] = True
default_prefs['checkforurlchange'] = True
default_prefs['injectseries'] = False
default_prefs['matchtitleauth'] = True
default_prefs['do_wordcount'] = SAVE_YES_UNLESS_SITE
default_prefs['smarten_punctuation'] = False
default_prefs['show_est_time'] = False
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2016, Jim Miller, 2011, Grant Drake <grant.drake@gmail.com>'
__docformat__ = 'restructuredtext en'
'''
A lot of this is lifted from Count Pages plugin by Grant Drake (with
some changes from davidfor.)
'''
import logging
logger = logging.getLogger(__name__)
import re
from calibre.ebooks.oeb.iterator import EbookIterator
RE_HTML_BODY = re.compile(u'<body[^>]*>(.*)</body>', re.UNICODE | re.DOTALL | re.IGNORECASE)
RE_STRIP_MARKUP = re.compile(u'<[^>]+>', re.UNICODE)
def get_word_count(book_path):
'''
Estimate a word count
'''
from calibre.utils.localization import get_lang
iterator = _open_epub_file(book_path)
lang = iterator.opf.language
lang = get_lang() if not lang else lang
count = _get_epub_standard_word_count(iterator, lang)
return count
def _open_epub_file(book_path, strip_html=False):
'''
Given a path to an EPUB file, read the contents into a giant block of text
'''
iterator = EbookIterator(book_path)
iterator.__enter__(only_input_plugin=True, run_char_count=True,
read_anchor_map=False)
return iterator
def _get_epub_standard_word_count(iterator, lang='en'):
'''
This algorithm counts individual words instead of pages
'''
book_text = _read_epub_contents(iterator, strip_html=True)
try:
from calibre.spell.break_iterator import count_words
wordcount = count_words(book_text, lang)
logger.debug('\tWord count - count_words method:%s'%wordcount)
except:
try: # The above method is new and no-one will have it as of 08/01/2016. Use an older method for a beta.
from calibre.spell.break_iterator import split_into_words_and_positions
wordcount = len(split_into_words_and_positions(book_text, lang))
logger.debug('\tWord count - split_into_words_and_positions method:%s'%wordcount)
except:
from calibre.utils.wordcount import get_wordcount_obj
wordcount = get_wordcount_obj(book_text)
wordcount = wordcount.words
logger.debug('\tWord count - old method:%s'%wordcount)
return wordcount
def _read_epub_contents(iterator, strip_html=False):
'''
Given an iterator for an ePub file, read the contents into a giant block of text
'''
book_files = []
for path in iterator.spine:
with open(path, 'rb') as f:
html = f.read().decode('utf-8', 'replace')
if strip_html:
html = unicode(_extract_body_text(html)).strip()
#print('FOUND HTML:', html)
book_files.append(html)
return ''.join(book_files)
def _extract_body_text(data):
'''
Get the body text of this html content wit any html tags stripped
'''
body = RE_HTML_BODY.findall(data)
if body:
return RE_STRIP_MARKUP.sub('', body[0]).replace('.','. ')
return ''
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -75,7 +75,6 @@ import adapter_ncisfictionnet
import adapter_thealphagatecom
import adapter_fanfiktionde
import adapter_ponyfictionarchivenet
import adapter_sg1heliopoliscom
import adapter_ncisficcom
import adapter_nationallibrarynet
import adapter_themasquenet
@@ -139,6 +138,7 @@ import adapter_forumquestionablequestingcom
import adapter_ninelivesarchivecom
import adapter_masseffect2in
import adapter_quotevcom
import adapter_mcstoriescom
## This bit of complexity allows adapters to be added by just adding
## importing. It eliminates the long if/else clauses we used to need
+21 -23
View File
@@ -23,7 +23,6 @@ import re
import urllib
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -40,15 +39,15 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
@staticmethod
def getSiteDomain():
return 'www.adastrafanfic.com'
@@ -66,14 +65,14 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
this and change it to True.
'''
return True
def extractChapterUrlsAndMetadata(self):
if self.is_adult or self.getConfig("is_adult"):
addurl = "&warning=5"
else:
addurl=""
url = self.url+'&index=1'+addurl
logger.debug("URL: "+url)
@@ -87,19 +86,19 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
if "Content is only suitable for mature adults. May contain explicit language and adult themes. Equivalent of NC-17." in data:
raise exceptions.AdultCheckRequired(self.url)
# problems with some stories, but only in calibre. I suspect
# issues with different SGML parsers in python. This is a
# nasty hack, but it works.
data = data[data.index("<body"):]
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
## Title
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"viewuser.php"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -126,7 +125,7 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
labels = soup.findAll('span',{'class':'label'})
for labelspan in labels:
@@ -136,8 +135,8 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ''
while value and not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while value and 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
# sometimes poorly formated desc (<p> w/o </p>) leads
# to all labels being included.
@@ -185,7 +184,7 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(value.strip(), "%d %b %Y"))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -198,7 +197,7 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self_make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -207,11 +206,11 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
def getChapterText(self, url):
@@ -222,15 +221,14 @@ class AdAstraFanficComSiteAdapter(BaseSiteAdapter):
# issues with different SGML parsers in python. This is a
# nasty hack, but it works.
data = data[data.index("<body"):]
soup = bs.BeautifulStoneSoup(data,
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(data)
span = soup.find('div', {'id' : 'story'})
if None == span:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,span)
def getClass():
@@ -42,35 +42,35 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
# 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.path.split('/',)[2])
# get storyId from url--url validation guarantees query correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL.
self._setURL('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')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%Y-%b-%d"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -84,7 +84,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
# http://archiveofourown.org/collections/Smallville_Slash_Archive/works/159770
# Discard leading zeros from story ID numbers--AO3 doesn't use them in it's own chapter URLs.
return r"https?://"+re.escape(self.getSiteDomain())+r"(/collections/[^/]+)?/works/0*(?P<id>\d+)"
## Login
def needToLoginCheck(self, data):
if 'This work is only available to registered users of the Archive.' in data \
@@ -92,7 +92,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url, data):
params = {}
@@ -110,10 +110,10 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
loginUrl = 'http://' + self.getSiteDomain() + '/user_sessions'
logger.info("Will now login to URL (%s) as (%s)" % (loginUrl,
params['user_session[login]']))
d = self._postUrl(loginUrl, params)
#logger.info(d)
if "Successfully logged in" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['user_session[login]']))
@@ -148,7 +148,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
if "This work could have adult content. If you proceed you have agreed that you are willing to see such content." in meta:
raise exceptions.AdultCheckRequired(self.url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
@@ -157,13 +157,13 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
if "Sorry, we couldn&#x27;t find the work you were looking for." in data:
raise exceptions.StoryDoesNotExist(self.url)
if self.needToLoginCheck(data):
# need to log in for this one.
self.performLogin(url,data)
data = self._fetchUrl(url,usecache=False)
meta = self._fetchUrl(metaurl,usecache=False)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
for tag in soup.findAll('div',id='admin-banner'):
@@ -173,7 +173,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
tag.extract()
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('a', href=re.compile(r"/works/\d+$"))
self.story.setMetadata('title',stripHTML(a))
@@ -190,6 +190,10 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
self.story.addToList('authorUrl',a['href'])
self.story.addToList('author',a.text)
byline = metasoup.find('h3',{'class':'byline'})
if byline:
self.story.setMetadata('byline',stripHTML(byline))
newestChapter = None
self.newestChapterNum = None # save for comparing during update.
# Scan all chapters to find the oldest and newest, on AO3 it's
@@ -242,7 +246,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
genres = a.findAll('a',{'class':"tag"})
for genre in genres:
self.story.addToList('freeformtags',genre.string)
a = metasoup.find('dd',{'class':"category tags"})
if a != None:
genres = a.findAll('a',{'class':"tag"})
@@ -255,7 +259,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
chars = a.findAll('a',{'class':"tag"})
for char in chars:
self.story.addToList('characters',char.string)
a = metasoup.find('dd',{'class':"relationship tags"})
if a != None:
ships = a.findAll('a',{'class':"tag"})
@@ -300,7 +304,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
@@ -343,7 +347,7 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
new_tag = soup.new_tag(tag)
new_tag.string=string
elem.append(new_tag)
if 'authorheadnotes' not in exclude_notes:
headnotes = soup.find('div', {'class' : "preface group"}).find('div', {'class' : "notes module"})
if headnotes != None:
@@ -351,14 +355,14 @@ class ArchiveOfOurOwnOrgAdapter(BaseSiteAdapter):
if headnotes != None:
append_tag(chapter,'b',"Author's Note:")
chapter.append(headnotes)
if 'chaptersummary' not in exclude_notes:
chapsumm = soup.find('div', {'id' : "summary"})
if chapsumm != None:
chapsumm = chapsumm.find('blockquote')
append_tag(chapter,'b',"Summary for the Chapter:")
chapter.append(chapsumm)
if 'chapterheadnotes' not in exclude_notes:
chapnotes = soup.find('div', {'id' : "notes"})
if chapnotes != None:
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class ArchiveSkyeHawkeComAdapter(BaseSiteAdapter):
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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/story.php?no='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ash')
# 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.
@@ -92,23 +92,23 @@ class ArchiveSkyeHawkeComAdapter(BaseSiteAdapter):
raise e
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('div', {'class':"story border"}).find('span',{'class':'left'})
title=stripHTML(a).split('"')[1]
self.story.setMetadata('title',title)
# Find authorid and URL from... author url.
author = a.find('a')
self.story.setMetadata('authorId',author['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+author['href'])
self.story.setMetadata('author',author.string)
authorSoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
authorSoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
chapter=soup.find('select',{'name':'chapter'}).findAll('option')
@@ -174,18 +174,17 @@ class ArchiveSkyeHawkeComAdapter(BaseSiteAdapter):
break
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div',{'class':"chapter bordersolid"}).findNext('div').findNext('div')
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
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])
# 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','asph')
# 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.
@@ -81,7 +81,7 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -95,13 +95,13 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
params['sid'] = ''
params['intent'] = ''
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['penname']))
d = self._fetchUrl(loginUrl, params)
if "Logout" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -129,29 +129,29 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
# 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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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)
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
try:
# in case link points somewhere other than the first chapter
a = soup.findAll('option')[1]['value']
self.story.setMetadata('storyId',a.split('=',)[1])
url = 'http://'+self.host+'/'+a
soup = bs.BeautifulSoup(self._fetchUrl(url))
soup = self.make_soup(self._fetchUrl(url))
except:
pass
@@ -194,7 +194,7 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
val = val.nextSibling
val = val.nextSibling
while not defaultGetattr(val) == 'table':
svalue += str(val)
svalue += unicode(val)
val = val.nextSibling
self.setDescription(url,svalue)
@@ -230,13 +230,13 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
@@ -244,11 +244,11 @@ class AshwinderSycophantHexComAdapter(BaseSiteAdapter):
data = self._fetchUrl(url)
soup = bs.BeautifulSoup(data, selfClosingTags=('br','hr','span','center')) # some chapters seem to be hanging up on those tags, so it is safer to close them
soup = self.make_soup(data) # some chapters seem to be hanging up on those tags, so it is safer to close them
story = soup.find('div', {"align" : "left"})
if None == story:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,story)
+21 -21
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -44,21 +44,21 @@ class Asr3SlashzoneOrgAdapter(BaseSiteAdapter):
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])
# 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','asr3')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -114,23 +114,23 @@ class Asr3SlashzoneOrgAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -158,7 +158,7 @@ class Asr3SlashzoneOrgAdapter(BaseSiteAdapter):
categorys = smalldiv.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for category in categorys:
self.story.addToList('category',category.string)
chars = smalldiv.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
self.story.addToList('characters',char.string)
@@ -191,7 +191,7 @@ class Asr3SlashzoneOrgAdapter(BaseSiteAdapter):
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))
seriessoup = self.make_soup(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
@@ -203,25 +203,25 @@ class Asr3SlashzoneOrgAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
# remove 'small' leaving only summary.
smalldiv.extract()
self.setDescription(url,metadiv)
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url))
soup = self.make_soup(self._fetchUrl(url))
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)
+20 -19
View File
@@ -23,7 +23,7 @@ import urllib2
import urlparse
import time
from .. import BeautifulSoup as bs
from bs4.element import Tag, Comment
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
@@ -113,7 +113,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
try:
data1 = self._fetchUrl(self.url)
soup = bs.BeautifulSoup(data1)
soup = self.make_soup(data1)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
@@ -121,7 +121,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
raise e
#strip comments from soup
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, bs.Comment))]
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
# Cache the soups so we won't have to redownload in getChapterText later
self.soupsCache = {}
@@ -137,7 +137,8 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
# TODO better metadata
date = soup.find("div", {"class": "submitted"}).string.strip()
date = re.sub(" &#151;.*", "", date)
# 11. April 2015 - 17:08
date = re.sub(r"(\d+\. \D+ \d+ - \d+:\d+).*", r"\1", date)
date = _translate_date_german_english(date)
self.story.setMetadata('datePublished', makeDate(date, self.dateformat))
title1 = soup.find("h1", {'class': 'title'}).string
@@ -179,7 +180,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
firstLink = "http://%s%s" % (self.getSiteDomain(), firstLinkDiv.findNext("a")['href'])
logger.debug("Found first chapter right away <%s>" % firstLink)
try:
soup = bs.BeautifulSoup(self._fetchUrl(firstLink))
soup = self.make_soup(self._fetchUrl(firstLink))
self.soupsCache[firstLink] = soup
self.chapterUrls.insert(0, (soup.find("h1").text, firstLink))
except urllib2.HTTPError, e:
@@ -202,14 +203,14 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
break
else:
logger.debug("Previous Chapter <%s>" % prevLink)
if type(prevLink) != bs.Tag or prevLink.name != "a":
if type(prevLink) != Tag or prevLink.name != "a":
prevLink = prevLink.findParent("a")
if prevLink is None or '#' in prevLink['href']:
logger.debug("Couldn't find prev part (false positive) <%s>" % prevLink)
break
prevLink = prevLink['href']
try:
soup = bs.BeautifulSoup(self._fetchUrl(prevLink))
soup = self.make_soup(self._fetchUrl(prevLink))
self.soupsCache[prevLink] = soup
prevTtitle = soup.find("h1", {'class': 'title'}).string
self.chapterUrls.insert(0, (prevTtitle, prevLink))
@@ -249,7 +250,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
logger.debug("Couldn't find next part")
break
else:
if type(nextLink) != bs.Tag or nextLink.name != "a":
if type(nextLink) != Tag or nextLink.name != "a":
nextLink = nextLink.findParent("a")
if nextLink is None or '#' in nextLink['href']:
logger.debug("Couldn't find next part (false positive) <%s>" % nextLink)
@@ -265,12 +266,12 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
nextLinkMatch = _REGEX_TRAILING_DIGIT.match(nextLink)
if nextLinkMatch is not None:
curChap = nextLinkMatch.group(1)
nextLink = re.sub(_REGEX_TRAILING_DIGIT, str(int(curChap) + 1), nextLink)
nextLink = re.sub(_REGEX_TRAILING_DIGIT, unicode(int(curChap) + 1), nextLink)
else:
break
try:
data = self._fetchUrl(nextLink)
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(nextLink)
@@ -280,7 +281,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
self.chapterUrls.append((title2, nextLink))
logger.debug("Grabbing next chapter URL " + nextLink)
self.soupsCache[nextLink] = soup
# [comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, bs.Comment))]
# [comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
logger.debug("Chapters: %s" % self.chapterUrls)
@@ -289,7 +290,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
curMax = self.maxChapter + step
lastHit = True
while True:
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, str(curMax), self.url)
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, unicode(curMax), self.url)
if nextChapterUrl == self.url:
logger.debug("Unable to guess next chapter because URL doesn't end in numbers")
break;
@@ -305,7 +306,7 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
if hit:
logger.debug("Found chapter URL " + nextChapterUrl)
self.maxChapter = curMax
self.soupsCache[nextChapterUrl] = bs.BeautifulSoup(data)
self.soupsCache[nextChapterUrl] = self.make_soup(data)
if not lastHit:
break
lastHit = curMax
@@ -316,8 +317,8 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
logger.debug(curMax)
for i in xrange(1, self.maxChapter):
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, str(i), self.url)
nextChapterTitle = re.sub("1", str(i), title1)
nextChapterUrl = re.sub(_REGEX_TRAILING_DIGIT, unicode(i), self.url)
nextChapterTitle = re.sub("1", unicode(i), title1)
self.chapterUrls.append((nextChapterTitle, nextChapterUrl))
def getChapterText(self, url):
@@ -328,15 +329,15 @@ class BdsmGeschichtenAdapter(BaseSiteAdapter):
else:
logger.debug('Downloading chapter <%s>' % url)
data1 = self._fetchUrl(url)
soup = bs.BeautifulSoup(data1)
soup = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, bs.Comment))]
[comment.extract() for comment in soup.findAll(text=lambda text:isinstance(text, Comment))]
# get story text
storyDiv1 = bs.Tag(soup, "div")
storyDiv1 = soup.new_tag("div")
for para in soup.find("div", "full-node").find('div', 'content').findAll("p"):
storyDiv1.append(para)
storyDiv1.append('<br />')
storyDiv1.append(soup.new_tag("br"))
storytext = self.utf8FromSoup(url,storyDiv1)
return storytext
@@ -3,7 +3,7 @@ import re
import urllib2
import urlparse
from .. import BeautifulSoup
from bs4 import BeautifulSoup
from ..htmlcleanup import stripHTML
from base_adapter import BaseSiteAdapter, makeDate
@@ -51,7 +51,7 @@ class BloodshedverseComAdapter(BaseSiteAdapter):
else:
data = self._fetchUrl(url, parameters)
return BeautifulSoup.BeautifulSoup(data)
return self.make_soup(data)
@staticmethod
def getSiteDomain():
+30 -31
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from bs4.element import Tag
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -68,22 +68,22 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/fiction/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','btf') # XXX
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %b %Y" # XXX
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -104,7 +104,7 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -116,13 +116,13 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/fiction/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -160,16 +160,16 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. nfacommunity uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=561&amp;warning=4
# viewstory.php?sid=561&amp;warning=1
# viewstory.php?sid=561&amp;warning=2
@@ -193,23 +193,23 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -243,8 +243,8 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
if 'Summary' in label:
## Everything until the next strong tag.
svalue = ""
while not isinstance(value,bs.Tag) or value.name != 'strong':
svalue += str(value)
while not isinstance(value,Tag) or value.name != 'strong':
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -277,7 +277,7 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
value=re.sub(r"\|",r"",value)
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
value=re.sub(r"\|",r"",value)
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
@@ -293,7 +293,7 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
for genre in genrestext:
self.story.addToList('genre',genre.string)
try:
# Find Series name from series URL.
a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
@@ -301,7 +301,7 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/fiction/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -310,22 +310,21 @@ class BloodTiesFansComAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+27 -28
View File
@@ -22,7 +22,7 @@ import re
import urllib2
import cookielib as cl
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -42,9 +42,9 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.setHeader()
self.decode = ["Windows-1252",
"utf8"] # 1252 is a superset of iso-8859-1.
# Most sites that claim to be
@@ -53,12 +53,12 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
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 correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL. gets rid of chapter if there, left with ch 1 URL on this site
nurl = "http://"+self.getSiteDomain()+"/fanfictions/index.php?act=vie&id="+self.story.getMetadata('storyId')
self._setURL(nurl)
@@ -69,8 +69,8 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','bnfnet')
@@ -84,11 +84,11 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
"Only needs to be overriden if URL contains more than one parameter"
## This adapter needs at least two parameters left on the URL, act and id
return re.sub(r"(\?act=(vie|ovr)&id=\d+)&.*$",r"\1",url)
def setHeader(self):
"buffynfaith.net wants a Referer for images. Used both above and below(after cookieproc added)"
self.opener.addheaders.append(('Referer', 'http://'+self.getSiteDomain()+'/'))
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=vie&id=1234 http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=ovr&id=1234 http://"+cls.getSiteDomain()+"/fanfictions/index.php?act=vie&id=1234&ch=2"
@@ -107,13 +107,13 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
this and change it to True.
'''
return True
def extractChapterUrlsAndMetadata(self):
dateformat = "%d %B %Y"
url = self.url
logger.debug("URL: "+url)
#set a cookie to get past adult check
if self.is_adult or self.getConfig("is_adult"):
cookie = cl.Cookie(version=0, name='my_age', value='yes',
@@ -127,7 +127,7 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False)
self.cookiejar.set_cookie(cookie)
self.get_cookiejar().set_cookie(cookie)
self.setHeader()
try:
@@ -140,22 +140,22 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
#print data
if "ADULT CONTENT WARNING" in data:
if "ADULT CONTENT WARNING" in data:
raise exceptions.AdultCheckRequired(self.url)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# Now go hunting for all the meta data and the chapter list.
#stuff in <head>: description
svalue = soup.head.find('meta',attrs={'name':'description'})['content']
#self.story.setMetadata('description',svalue)
self.setDescription(url,svalue)
#useful stuff in rest of doc, all contained in this:
doc = soup.body.find('div', id='my_wrapper')
#first the site category (more of a genre to me, meh) and title, in this element:
mt = doc.find('div',attrs={'class':'maintitle'})
self.story.addToList('genre',mt.findAll('a')[1].string)
@@ -165,7 +165,7 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
#the actual category, for me, is 'Buffy: The Vampire Slayer'
#self.story.addToList('category','Buffy: The Vampire Slayer')
#No need to do it here, it is better to set it in in plugin-defaults.ini and defaults.ini
#then a block that sits in a table cell like so:
#(contains a lot of metadata)
mblock = doc.find('td', align='left', width = '70%').contents
@@ -204,9 +204,9 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
#end ifs
#end while
# Find the chapter selector
# Find the chapter selector
select = soup.find('select', { 'name' : 'ch' } )
if select is None:
# no selector found, so it's a one-chapter story.
#self.chapterUrls.append((self.story.getMetadata('title'),url))
@@ -223,10 +223,10 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
title = ts[0]+'. '+ts[1]
self.chapterUrls.append((title,url))
self.story.setMetadata('numChapters',len(self.chapterUrls))
## Go scrape the rest of the metadata from the author's page.
data = self._fetchUrl(self.story.getMetadata('authorUrl'))
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
#find the story link and its parent div
storya = soup.find('a',{'href':self.story.getMetadata('storyUrl')})
storydiv = storya.parent
@@ -269,22 +269,21 @@ class BuffyNFaithNetAdapter(BaseSiteAdapter):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'fanfiction'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
#remove all the unnecessary bookmark tags
[s.extract() for s in div('div',{'class':"tiny_box2"})]
#is there a review link?
r = div.find('a',href=re.compile(re.escape("./index.php?act=irv")+".*$"))
if r is not None:
#remove the review link and its parent div
r.parent.extract()
#There might also be a link to the sequel on the last chapter
#I'm inclined to keep it in, but the URL needs to be changed from relative to absolute
#Shame there isn't proper series metadata available
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
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])
# 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','csph')
# 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.
@@ -104,21 +104,21 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
# the title search to troubleshoot.
if "Age Consent Required" in data:
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -155,8 +155,8 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
while value != None:
val = value
value = value.previousSibling
while not defaultGetattr(val,'class') == 'label':
svalue += str(val)
while 'label' not in defaultGetattr(val,'class'):
svalue += unicode(val)
val = val.nextSibling
self.setDescription(url,svalue)
@@ -195,7 +195,7 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.split(' -')[0]), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -208,7 +208,7 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -217,22 +217,21 @@ class ChaosSycophantHexComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+34 -34
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
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])
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','csiforensics')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %b %Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -101,24 +101,24 @@ class CSIForensicsComAdapter(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 "This story is rated NC-17, and therefore is not suitable for minors. If you are below the age required to view such material in your locality, please return from whence you came." in data: # XXX
if "This story is rated NC-17, and therefore is not suitable for minors. If you are below the age required to view such material in your locality, please return from whence you came." in data: # XXX
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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.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])
@@ -136,10 +136,10 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
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:
@@ -148,29 +148,29 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
return ""
smalldiv = soup.find('div', {'class' : 'small'})
chars = smalldiv.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
self.story.addToList('characters',char.string)
metatext = stripHTML(smalldiv)
if 'Completed: Yes' in metatext:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
self.story.setMetadata('status', 'In-Progress')
word=soup.find(text=re.compile("Word count:")).split(':')
self.story.setMetadata('numWords', word[1])
cats = smalldiv.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for cat in cats:
self.story.addToList('category',cat.string)
warnings = smalldiv.findAll('a',href=re.compile(r'browse.php\?type=class(&amp;)type_id=2(&amp;)classid=\d+'))
for warning in warnings:
self.story.addToList('warnings',warning.string)
self.story.addToList('warnings',warning.string)
date=soup.find('div',{'class' : 'bottom'})
pd=date.find(text=re.compile("Published:")).string.split(': ')
@@ -189,7 +189,7 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
for genre in genres:
self.story.addToList('genre',genre.string)
if 'Warnings' in label:
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)
@@ -201,7 +201,7 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -210,28 +210,28 @@ class CSIForensicsComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
smalldiv.extract()
# Summary
# Summary
summary = soup.find('div', {'class' : 'content'})
self.setDescription(url,summary)
self.setDescription(url,summary)
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url))
soup = self.make_soup(self._fetchUrl(url))
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)
+3 -3
View File
@@ -31,11 +31,11 @@ class DarkSolaceOrgAdapter(BaseEfictionAdapter):
@classmethod
def getSiteAbbrev(self):
return 'dksl'
@classmethod
def getDateFormat(self):
return "%B %d, %Y"
def getClass():
return DarkSolaceOrgAdapter
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
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])
# 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','dgrfa')
# 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.
@@ -117,23 +117,23 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -166,8 +166,8 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while value and 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -201,7 +201,7 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -214,7 +214,7 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -223,22 +223,21 @@ class DestinysGatewayComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+61 -62
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -45,10 +45,10 @@ class DokugaComAdapter(BaseSiteAdapter):
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.path.split('/',)[3])
# www.dokuga.com has two 'sections', shown in URL as
# 'fanfiction' and 'spark' that change how things should be
@@ -56,10 +56,10 @@ class DokugaComAdapter(BaseSiteAdapter):
# http://www.dokuga.com/fanfiction/story/7528/1
# http://www.dokuga.com/spark/story/7299/1
self.section=self.parsedUrl.path.split('/',)[1]
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/'+self.parsedUrl.path.split('/',)[1]+'/story/'+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','dkg')
@@ -69,7 +69,7 @@ class DokugaComAdapter(BaseSiteAdapter):
self.dateformat = "%d %b %Y"
else:
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.
@@ -88,7 +88,7 @@ class DokugaComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url,soup):
params = {}
@@ -103,11 +103,11 @@ class DokugaComAdapter(BaseSiteAdapter):
# copy all hidden input tags to pick up appropriate tokens.
for tag in soup.findAll('input',{'type':'hidden'}):
params[tag['name']] = tag['value']
loginUrl = 'http://' + self.getSiteDomain() + '/fanfiction'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['username']))
d = self._postUrl(loginUrl, params)
if "Your session has expired. Please log in again." in d:
@@ -136,25 +136,25 @@ class DokugaComAdapter(BaseSiteAdapter):
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
if self.needToLoginCheck(data):
# need to log in for this one.
self.performLogin(url,soup)
data = self._fetchUrl(url)
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
if "Access denied. This story has not been validated by the adminstrators of this site." in data:
raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
raise exceptions.AccessDenied(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
# print data
# Now go hunting for all the meta data and the chapter list.
## Title and author
a = soup.find('div', {'align' : 'center'}).find('h3')
# Find authorid and URL from... author url.
aut = a.find('a')
self.story.setMetadata('authorId',aut['href'].split('=')[1])
@@ -162,7 +162,7 @@ class DokugaComAdapter(BaseSiteAdapter):
self.story.setMetadata('authorUrl','http://'+self.host+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
a = a.string[:(len(a.string)-4)]
self.story.setMetadata('title',stripHTML(a))
@@ -176,12 +176,12 @@ class DokugaComAdapter(BaseSiteAdapter):
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+self.section+'/story/'+self.story.getMetadata('storyId')+'/'+chapter['value']))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(alink))
asoup = self.make_soup(self._fetchUrl(alink))
if 'fanfiction' in self.section:
asoup=asoup.find('div', {'id' : 'cb_tabid_52'}).find('div')
#grab the rest of the metadata from the author's page
for div in asoup.findAll('div'):
nav=div.find('a', href=re.compile(r'/fanfiction/story/'+self.story.getMetadata('storyId')+"/1$"))
@@ -189,90 +189,89 @@ class DokugaComAdapter(BaseSiteAdapter):
break
div=div.nextSibling
self.setDescription(url,div)
div=div.nextSibling
a=div.text.split('Rating: ')
if len(a) == 2: self.story.setMetadata('rating', a[1].split('&')[0])
a=div.text.split('Status: ')
a=div.string.split('Rating: ')
if len(a) == 2: self.story.setMetadata('rating', a[1].split('-')[0])
a=div.string.split('Status: ')
if len(a)==2:
iscomp=a[1].split('&')[0]
iscomp=a[1].split('-')[0]
if 'Complete' in iscomp:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
a=div.text.split('Category: ')
if len(a) == 2: self.story.addToList('category', a[1].split('&')[0])
a=div.text.split('Created: ')
if len(a) == 2: self.story.setMetadata('datePublished', makeDate(stripHTML(a[1].split('&')[0]), self.dateformat))
a=div.text.split('Updated: ')
a=div.string.split('Category: ')
if len(a) == 2: self.story.addToList('category', a[1].split('-')[0])
a=div.string.split('Created: ')
if len(a) == 2: self.story.setMetadata('datePublished', makeDate(stripHTML(a[1].split('-')[0]), self.dateformat))
a=div.string.split('Updated: ')
if len(a) == 2: self.story.setMetadata('dateUpdated', makeDate(stripHTML(a[1]), self.dateformat))
div=div.nextSibling.nextSibling
a=div.text.split('Words: ')
if len(a) == 2: self.story.setMetadata('numWords', a[1].split('&')[0])
a=div.text.split('Genre: ')
a=div.string.split('Words: ')
if len(a) == 2: self.story.setMetadata('numWords', a[1].split('-')[0])
a=div.string.split('Genre: ')
if len(a) == 2:
for genre in a[1].split('&')[0].split(', '):
for genre in a[1].split('-')[0].split(', '):
self.story.addToList('genre',genre)
else:
asoup=asoup.find('div', {'id' : 'maincol'}).find('div', {'class' : 'padding'})
for div in asoup.findAll('div'):
nav=div.find('a', href=re.compile(r'/spark/story/'+self.story.getMetadata('storyId')+"/1$"))
if nav != None:
break
div=div.nextSibling.nextSibling
self.setDescription(url,div)
self.story.addToList('category', 'Spark')
div=div.nextSibling.nextSibling
a=div.text.split('Rating: ')
a=div.string.split('Rating: ')
if len(a) == 2: self.story.setMetadata('rating', a[1].split(' - ')[0])
a=div.text.split('Status: ')
a=div.string.split('Status: ')
if len(a)==2:
iscomp=a[1].split(' - ')[0]
if 'Complete' in iscomp:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
a=div.text.split('Genre: ')
a=div.string.split('Genre: ')
if len(a)==2:
for genre in a[1].split(' - ')[0].split('/'):
self.story.addToList('genre',genre)
div=div.nextSibling.nextSibling
a=div.text.split('Updated: ')
a=div.string.split('Updated: ')
if len(a)==2:
date=a[1].split(' -')[0]
self.story.setMetadata('dateUpdated', makeDate(date, self.dateformat))
# does not have published date anywhere
self.story.setMetadata('datePublished', makeDate(date, self.dateformat))
a=div.text.split('Words ')
a=div.string.split('Words ')
if len(a)==2: self.story.setMetadata('numWords', a[1])
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'chtext'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+33 -34
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
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])
# 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.
@@ -81,7 +81,7 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +93,13 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -156,23 +156,23 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -198,17 +198,17 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
# <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
@@ -216,14 +216,14 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while value and 'label' not in defaultGetattr(value,'class'):
svalue += unicode(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)
@@ -254,8 +254,8 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
self.story.setMetadata('status', 'In-Progress')
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.nextSibling).split(' |')[0], self.dateformat))
self.story.setMetadata('datePublished', makeDate(stripHTML(value).split(' |')[0], self.dateformat))
if 'Updated' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
@@ -266,7 +266,7 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -278,25 +278,24 @@ class DracoAndGinnyComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+26 -27
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from bs4.element import Tag
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -47,21 +47,21 @@ class DramioneOrgAdapter(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])
# 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','drmn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %B %Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -82,7 +82,7 @@ class DramioneOrgAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -94,13 +94,13 @@ class DramioneOrgAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -144,20 +144,20 @@ class DramioneOrgAdapter(BaseSiteAdapter):
# the title search to troubleshoot.
if "Stories that are suitable for ages 16 and older" in data:
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -229,8 +229,8 @@ class DramioneOrgAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next strong tag.
svalue = ""
while not isinstance(value,bs.Tag) or value.name != 'strong':
svalue += str(value)
while not isinstance(value,Tag) or value.name != 'strong':
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
@@ -262,7 +262,7 @@ class DramioneOrgAdapter(BaseSiteAdapter):
if 'Published' in label:
value=re.sub(r"(\d+)(st|nd|rd|th)",r"\1",value)
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
value=re.sub(r"(\d+)(st|nd|rd|th)",r"\1",value)
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
@@ -274,7 +274,7 @@ class DramioneOrgAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -283,11 +283,11 @@ class DramioneOrgAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
try:
self.story.setMetadata('reviews',
stripHTML(soup.find('h2',{'id':'pagetitle'}).
@@ -295,18 +295,17 @@ class DramioneOrgAdapter(BaseSiteAdapter):
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
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])
# 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','eesd')
# 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.
@@ -72,7 +72,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -89,15 +89,15 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
# Now go hunting for all the meta data and the chapter list.
## Title and author
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
@@ -106,7 +106,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = pagetitle.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -138,7 +138,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
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)
labels = list.findAll('b')
for labelspan in labels:
value = labelspan.nextSibling
@@ -147,8 +147,8 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while 'Rating' not in str(value):
svalue += str(value)
while 'Rating' not in unicode(value):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -177,7 +177,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -189,7 +189,7 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
# Find Series name from series URL.
series_url = 'http://'+self.host+'/'+series['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -203,22 +203,21 @@ class EfictionEstelielDeAdapter(BaseSiteAdapter):
i+=1
if i == 0:
break
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+37 -37
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class EFPFanFicNet(BaseSiteAdapter):
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])
# 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','efp')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -80,7 +80,7 @@ class EFPFanFicNet(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -96,9 +96,9 @@ class EFPFanFicNet(BaseSiteAdapter):
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?sid='+self.story.getMetadata('storyId')
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['penname']))
d = self._fetchUrl(loginUrl, params)
if '<a class="menu" href="newaccount.php">' in d : # register for new account link
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -127,27 +127,27 @@ class EFPFanFicNet(BaseSiteAdapter):
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.")
# raise exceptions.AccessDenied(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)
soup = self.make_soup(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')+"$"))
a = soup.find('a', href=re.compile(r'^viewstory\.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# 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 chapter selector
# Find the chapter selector
select = soup.find('select', { 'name' : 'sid' } )
if select is None:
# no selector found, so it's a one-chapter story.
self.chapterUrls.append((self.story.getMetadata('title'),url))
@@ -162,13 +162,13 @@ class EFPFanFicNet(BaseSiteAdapter):
self.story.setMetadata('numChapters',len(self.chapterUrls))
self.story.setMetadata('language','Italian')
# normalize story URL to first chapter if later chapter URL was given:
url = self.chapterUrls[0][1].replace('&i=1','')
logger.debug("Normalizing to URL: "+url)
self._setURL(url)
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# eFiction sites don't help us out a lot with their meta data
# formating, so it's a little ugly.
@@ -177,7 +177,7 @@ class EFPFanFicNet(BaseSiteAdapter):
authsoup = None
storyblock = None
authurl = self.story.getMetadata('authorUrl')
## author can have more than one page of stories.
while storyblock == None:
@@ -186,10 +186,10 @@ class EFPFanFicNet(BaseSiteAdapter):
# last author link with offset should be the 'next' link.
authurl = u'http://%s/%s' % ( self.getSiteDomain(),
authsoup.findAll('a',href=re.compile(r'viewuser\.php\?uid=\d+&catid=&offset='))[-1]['href'] )
# Need author page for most of the metadata.
logger.debug("fetching author page: (%s)"%authurl)
authsoup = bs.BeautifulSoup(self._fetchUrl(authurl))
authsoup = self.make_soup(self._fetchUrl(authurl))
#print("authsoup:%s"%authsoup)
storyas = authsoup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+r'&i=1$'))
@@ -204,7 +204,7 @@ class EFPFanFicNet(BaseSiteAdapter):
noteblock = storyblock.find('div', {'class':'notebloc'})
#print("%s"%noteblock)
notetext = ("%s" % noteblock).replace("<br />"," |")
notetext = ("%s" % noteblock).replace("<br/>"," |")
# <div class="notebloc">Autore: <a href="viewuser.php?uid=243036">Cendrillon89</a> | Pubblicata: 23/10/12 | Aggiornata: 30/10/12 | Rating: Arancione | Genere: Drammatico, Sentimentale | Capitoli: 10 | Completa<br />
# Tipo di coppia: Het | Personaggi: Akasuna no Sasori , Akatsuki, Nuovo Personaggio | Note: OOC | Avvertimenti: Tematiche delicate<br />
# Categoria: <a href="categories.php?catid=1&amp;parentcatid=1">Anime & Manga</a> > <a href="categories.php?catid=108&amp;parentcatid=108">Naruto</a> | Contesto: Naruto Shippuuden | Leggi le <a href="reviews.php?sid=1331275&amp;a=">3</a> recensioni</div>
@@ -212,7 +212,7 @@ class EFPFanFicNet(BaseSiteAdapter):
cats = noteblock.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for cat in cats:
self.story.addToList('category',cat.string)
for item in notetext.split("|"):
if ":" in item:
(label,value) = item.split(":")
@@ -223,13 +223,13 @@ class EFPFanFicNet(BaseSiteAdapter):
if 'Pubblicata' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Aggiornata' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
if label == "Completa":
self.story.setMetadata('status', 'Completed')
if label == "In corso":
self.story.setMetadata('status', 'In-Progress')
@@ -239,11 +239,11 @@ class EFPFanFicNet(BaseSiteAdapter):
if 'Personaggi' in label:
for val in value.split(","):
self.story.addToList('characters',val)
if 'Genere' in label:
for val in value.split(","):
self.story.addToList('genre',val)
if 'Coppie' in label:
for val in value.split(","):
self.story.addToList('ships',val)
@@ -254,21 +254,21 @@ class EFPFanFicNet(BaseSiteAdapter):
self.story.addToList('warnings',val)
# 'extra' metadata for this adapter:
if 'Tipo di coppia' in label:
for val in value.split(","):
self.story.addToList('type',val)
if 'Note' in label:
for val in value.split(","):
if val != "None":
self.story.addToList('notes',val)
if 'Contesto' in label:
self.story.setMetadata('context', value)
## Note--efp doesn't provide word count.
try:
# Find Series name from series URL.
a = soup.find('a', href=re.compile(r"viewseries.php\?ssid=\d+&i=1"))
@@ -276,7 +276,7 @@ class EFPFanFicNet(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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'))
i=1
@@ -286,23 +286,23 @@ class EFPFanFicNet(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url))
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'class' : 'storia'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
# remove any header and 'o:p' tags.
for tag in div.findAll("head") + div.findAll("o:p"):
tag.extract()
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
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])
# 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','essph')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -117,24 +117,24 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -171,8 +171,8 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
while value != None:
val = value
value = value.previousSibling
while not defaultGetattr(val,'class') == 'label':
svalue += str(val)
while 'label' not in defaultGetattr(val,'class'):
svalue += unicode(val)
val = val.nextSibling
self.setDescription(url,svalue)
@@ -211,7 +211,7 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.split(' -')[0]), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -224,7 +224,7 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
i=1
for a in storyas:
@@ -235,22 +235,21 @@ class ErosnSapphoSycophantHexComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -61,22 +61,22 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
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','csltv') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -97,7 +97,7 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -109,13 +109,13 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -172,15 +172,15 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
@@ -189,17 +189,17 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = pagetitle.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)
# Reviews
reviewdata = soup.find('div', {'id' : 'sort'})
a = reviewdata.findAll('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
self.story.setMetadata('reviews',stripHTML(a))
# Find the chapters:
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
@@ -227,8 +227,8 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while value and 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -279,7 +279,7 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -292,7 +292,7 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -301,22 +301,21 @@ class FanficCastleTVNetAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+1 -3
View File
@@ -4,8 +4,6 @@ import re
import urllib2
import urlparse
from .. import BeautifulSoup
from base_adapter import BaseSiteAdapter, makeDate
from .. import exceptions
@@ -55,7 +53,7 @@ class FanficHuAdapter(BaseSiteAdapter):
else:
data = self._fetchUrl(url, parameters)
return BeautifulSoup.BeautifulSoup(data)
return self.make_soup(data)
@staticmethod
def getSiteDomain():
@@ -4,8 +4,7 @@ import re
import urllib2
import urlparse
from .. import BeautifulSoup
from bs4.element import Tag
from base_adapter import BaseSiteAdapter, makeDate
from .. import exceptions
@@ -65,7 +64,7 @@ class FanfictionCsodaidokHuAdapter(BaseSiteAdapter):
else:
data = self._fetchUrl(url, parameters)
return BeautifulSoup.BeautifulSoup(data)
return self.make_soup(data)
@staticmethod
def getSiteDomain():
@@ -147,7 +146,7 @@ class FanfictionCsodaidokHuAdapter(BaseSiteAdapter):
keep_summary_html = self.getConfig('keep_summary_html')
for sibling in _yield_next_siblings(element):
if isinstance(sibling, BeautifulSoup.Tag):
if isinstance(sibling, Tag):
if sibling.name == 'span' and sibling.get('class', None) == 'classification':
break
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -70,22 +70,22 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/efiction/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ffjde') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -106,7 +106,7 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -118,13 +118,13 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/efiction/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -166,33 +166,33 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
# 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 "For adults only " in data: # XXX
if "For adults only " in data: # XXX
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
pagetitle = soup.find('h4')
## Title
## Title
a = pagetitle.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 = pagetitle.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+'/efiction/'+a['href'])
self.story.setMetadata('author',a.string)
# Reviews
reviewdata = soup.find('div', {'id' : 'sort'})
a = reviewdata.findAll('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
self.story.setMetadata('reviews',stripHTML(a))
# Find the chapters:
for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
@@ -210,12 +210,12 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
list = soup.find('div', {'class':'listbox'})
labels = list.findAll('b')
for labelspan in labels:
value = labelspan.nextSibling
@@ -227,7 +227,7 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
if 'Eingestuft' in label:
self.story.setMetadata('rating', value)
if 'W&ouml;rter' in label:
if u'Wörter' in label:
self.story.setMetadata('numWords', value)
if 'Kategorie' in label:
@@ -246,9 +246,9 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
else:
self.story.setMetadata('status', 'In-Progress')
if 'Ver&ouml;ffentlicht' in label:
if u'Veröffentlicht' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Aktualisiert' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -261,7 +261,7 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/efiction/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -270,22 +270,21 @@ class FanfictionJunkiesDeAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+21 -21
View File
@@ -38,7 +38,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.story.setMetadata('siteabbrev','ffnet')
# get storyId from url--url validation guarantees second part is storyId
self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2])
@@ -90,7 +90,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
this and change it to True.
'''
return True
def doExtractChapterUrlsAndMetadata(self,get_cover=True):
# fetch the chapter. From that we will get almost all the
@@ -109,7 +109,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
raise exceptions.StoryDoesNotExist(url)
else:
raise e
if "Unable to locate story" in data:
raise exceptions.StoryDoesNotExist(url)
@@ -145,7 +145,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
except e:
logger.warn("Caught an exception reading URL: %s sleeptime(%s) Exception %s."%(unicode(url),sleeptime,unicode(e)))
pass
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"^/u/\d+"))
self.story.setMetadata('authorId',a['href'].split('/')[2])
@@ -180,14 +180,14 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
logger.info("Fall back category collection")
for c in stripHTML(categories[0]).replace(" Crossover","").split(' + '):
self.story.addToList('category',c)
a = soup.find('a', href=re.compile(r'https?://www\.fictionratings\.com/'))
rating = a.string
if 'Fiction' in rating: # if rating has 'Fiction ', strip that out for consistency with past.
rating = rating[8:]
self.story.setMetadata('rating',rating)
# after Rating, the same bit of text containing id:123456 contains
@@ -199,7 +199,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
summarydiv = gui_table1i.find('div',{'style':'margin-top:2px'})
if summarydiv:
self.setDescription(url,stripHTML(summarydiv))
grayspan = gui_table1i.find('span', {'class':'xgray xcontrast_txt'})
# for b in grayspan.findAll('button'):
@@ -210,7 +210,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
#logger.debug("metalist:(%s)"%metalist)
# Rated: Fiction K - English - Words: 158,078 - Published: 02-04-11
# Rated: Fiction T - English - Adventure/Sci-Fi - Naruto U. - Chapters: 22 - Words: 114,414 - Reviews: 395 - Favs: 779 - Follows: 835 - Updated: 03-21-13 - Published: 04-28-12 - id: 8067258
# Rated: Fiction T - English - Adventure/Sci-Fi - Naruto U. - Chapters: 22 - Words: 114,414 - Reviews: 395 - Favs: 779 - Follows: 835 - Updated: 03-21-13 - Published: 04-28-12 - id: 8067258
# rating is obtained above more robustly.
if metalist[0].startswith('Rated:'):
@@ -239,7 +239,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
# updated get set to the same as published upstream if not found.
self.story.setMetadata('dateUpdated',datetime.fromtimestamp(float(dates[0]['data-xutime'])))
self.story.setMetadata('datePublished',datetime.fromtimestamp(float(dates[-1]['data-xutime'])))
donechars = False
while len(metalist) > 0:
if metalist[0].startswith('Chapters') or metalist[0].startswith('Status') or metalist[0].startswith('id:') or metalist[0].startswith('Updated:') or metalist[0].startswith('Published:'):
@@ -294,7 +294,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
authimg_url=img[0]['src']
logger.debug("authimg_url:%s"%authimg_url)
## ffnet uses different sizes on auth & story pages, but same id.
## //ffcdn2012t-fictionpressllc.netdna-ssl.com/image/1936929/150/
## //ffcdn2012t-fictionpressllc.netdna-ssl.com/image/1936929/180/
@@ -310,14 +310,14 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
## don't use cover if it matches the auth image.
if cover_id and authimg_id and cover_id == authimg_id:
cover_url = None
if cover_url:
self.setCoverImage(url,cover_url)
# Find the chapter selector
# Find the chapter selector
select = soup.find('select', { 'name' : 'chapter' } )
if select is None:
# no selector found, so it's a one-chapter story.
self.chapterUrls.append((self.story.getMetadata('title'),url))
@@ -346,7 +346,7 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
if "Please email this error message in full to <a href='mailto:support@fanfiction.com'>support@fanfiction.com</a>" in data:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! FanFiction.net Site Error!" % 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 role='main'" and
@@ -356,9 +356,9 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
# if divstr not in data:
# raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
# else:
# data = data[data.index(divstr):]
# data = data[data.index(divstr):]
# data = data.replace("<body","<notbody").replace("<BODY","<NOTBODY")
soup = self.make_soup(data)
## Remove the 'share' button.
@@ -366,9 +366,9 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
# sharediv = soup.find('div', {'class' : 'a2a_kit a2a_default_style'})
# if sharediv:
# sharediv.extract()
div = soup.find('div', {'id' : 'storytextp'})
if None == div:
logger.debug('div id=storytextp not found. data:%s'%data)
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+22 -22
View File
@@ -22,7 +22,7 @@ import re
import urllib
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -47,21 +47,21 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
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.path.split('/',)[2])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/s/'+self.story.getMetadata('storyId') + '/1')
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ffde')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d.%m.%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -73,14 +73,14 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/s/")+r"\w+(/\d+)?"
def use_pagecache(self):
'''
adapters that will work with the page cache need to implement
this and change it to True.
'''
return True
## Login seems to be reasonably standard across eFiction sites.
def needToLoginCheck(self, data):
if 'Diese Geschichte wurde als entwicklungsbeeintr' in data \
@@ -89,7 +89,7 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self,url):
params = {}
@@ -106,7 +106,7 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
loginUrl = 'https://ssl.fanfiktion.de/'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['nickname']))
soup = bs.BeautifulSoup(self._postUrl(loginUrl,params))
soup = self.make_soup(self._postUrl(loginUrl,params))
if not soup.find('a', title='Logout'):
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['nickname']))
@@ -128,25 +128,25 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
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,usecache=False)
if "Uhr ist diese Geschichte nur nach einer" in data:
raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Auserhalb der Zeit von 23:00 Uhr bis 04:00 Uhr ist diese Geschichte nur nach einer erfolgreichen Altersverifikation zuganglich.")
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('a', href=re.compile(r'/s/'+self.story.getMetadata('storyId')+"/"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
head = soup.find('div', {'class' : 'story-left'})
a = head.find('a')
@@ -167,11 +167,11 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
# second colspan=3 td in head.
genres=stripHTML(head.find('span',class_='fa-angle-right').next_sibling)
self.story.extendList('genre',genres[:genres.index('/')].split(', '))
if head.find('span',title='Fertiggestellt'):
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In Progress')
self.story.setMetadata('status', 'In-Progress')
#find metadata on the author's page
asoup = self.make_soup(self._fetchUrl("http://"+self.getSiteDomain()+"?a=q&a1=v&t=nickdetailsstories&lbi=stories&ar=0&nick="+self.story.getMetadata('authorId')))
@@ -181,13 +181,13 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
if '/s/'+self.story.getMetadata('storyId')+'/1/' in a['href']:
break
self.setDescription(url,a['onmouseover'].split("', '")[1])
td = tr[i].findAll('td')
self.story.addToList('category',stripHTML(td[2]))
self.story.setMetadata('rating', stripHTML(td[5]))
self.story.setMetadata('numWords', stripHTML(td[6]))
# grab the text for an individual chapter.
def getChapterText(self, url):
@@ -195,12 +195,12 @@ class FanFiktionDeAdapter(BaseSiteAdapter):
time.sleep(0.5) ## ffde has "floodlock" protection
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'storytext'})
for a in div.findAll('script'):
a.extract()
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+7 -7
View File
@@ -32,11 +32,11 @@ class FHSArchiveComAdapter(BaseEfictionAdapter):
@classmethod
def getSiteAbbrev(self):
return 'fhsa'
@classmethod
def getDateFormat(self):
return "%m/%d/%y"
return "%m/%d/%y"
def handleMetadataPair(self, key, value):
if key == 'Warnings':
for val in re.split("\s*,\s*", value):
@@ -45,13 +45,13 @@ class FHSArchiveComAdapter(BaseEfictionAdapter):
else:
# toss numbers only.
self.story.addToList('warnings', filter(lambda x : not x.isdigit() , val))
# elif 'Categories' in key:
# for val in re.split("\s*>\s*", value):
# self.story.addToList('category', val)
else:
super(FHSArchiveComAdapter, self).handleMetadataPair(key, value)
super(FHSArchiveComAdapter, self).handleMetadataPair(key, value)
def getClass():
return FHSArchiveComAdapter
+36 -36
View File
@@ -23,7 +23,7 @@ import re
import urllib2
from .. import translit
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -49,21 +49,21 @@ class FicBookNetAdapter(BaseSiteAdapter):
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.path.split('/',)[2])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/readfic/'+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','fbn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %m %Y"
self.dateformat = "%d %m %Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -90,17 +90,17 @@ class FicBookNetAdapter(BaseSiteAdapter):
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# Now go hunting for all the meta data and the chapter list.
table = soup.find('td',{'width':'50%'})
## Title
a = soup.find('h1')
self.story.setMetadata('title',stripHTML(a))
logger.debug("Title: (%s)"%self.story.getMetadata('title'))
# Find authorid and URL from... author url.
a = table.find('a')
self.story.setMetadata('authorId',a.text) # Author's name is unique
@@ -118,9 +118,9 @@ class FicBookNetAdapter(BaseSiteAdapter):
churl='http://'+self.host+chapter['href']
self.chapterUrls.append((stripHTML(chapter),churl))
if x == 0:
pubdate = translit.translit(stripHTML(bs.BeautifulSoup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
pubdate = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
if x == len(chapters)-1:
update = translit.translit(stripHTML(bs.BeautifulSoup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
update = translit.translit(stripHTML(self.make_soup(self._fetchUrl(churl)).find('div', {'class' : 'part_added'}).find('span')))
else:
self.chapterUrls.append((self.story.getMetadata('title'),url))
self.story.setMetadata('numChapters',1)
@@ -135,19 +135,20 @@ class FicBookNetAdapter(BaseSiteAdapter):
update=datetime.date.today().strftime(self.dateformat)
pubdate=pubdate.split(',')[0]
update=update.split(',')[0]
fullmon = {"yanvarya":"01", "января":"01",
"fievralya":"02", "февраля":"02",
"marta":"03", "марта":"03",
"aprielya":"04", "апреля":"04",
"maya":"05", "мая":"05",
"iyunya":"06", "июня":"06",
"iyulya":"07", "июля":"07",
"avghusta":"08", "августа":"08",
"sentyabrya":"09", "сентября":"09",
"oktyabrya":"10", "октября":"10",
"noyabrya":"11", "ноября":"11",
"diekabrya":"12", "декабря":"12" }
fullmon = {"yanvarya":"01", u"января":"01",
"fievralya":"02", u"февраля":"02",
"marta":"03", u"марта":"03",
"aprielya":"04", u"апреля":"04",
"maya":"05", u"мая":"05",
"iyunya":"06", u"июня":"06",
"iyulya":"07", u"июля":"07",
"avghusta":"08", u"августа":"08",
"sentyabrya":"09", u"сентября":"09",
"oktyabrya":"10", u"октября":"10",
"noyabrya":"11", u"ноября":"11",
"diekabrya":"12", u"декабря":"12" }
for (name,num) in fullmon.items():
if name in pubdate:
pubdate = pubdate.replace(name,num)
@@ -160,12 +161,12 @@ class FicBookNetAdapter(BaseSiteAdapter):
pr=soup.find('a', href=re.compile(r'/printfic/\w+'))
pr='http://'+self.host+pr['href']
pr = bs.BeautifulSoup(self._fetchUrl(pr))
pr = self.make_soup(self._fetchUrl(pr))
pr=pr.findAll('div', {'class' : 'part_text'})
i=0
for part in pr:
i=i+len(stripHTML(part).split(' '))
self.story.setMetadata('numWords', str(i))
self.story.setMetadata('numWords', unicode(i))
i=0
fandoms = table.findAll('a', href=re.compile(r'/fanfiction/\w+'))
@@ -173,13 +174,13 @@ class FicBookNetAdapter(BaseSiteAdapter):
self.story.addToList('category',fandom.string)
i=i+1
if i > 1:
self.story.addToList('genre', 'Кроссовер')
self.story.addToList('genre', u'Кроссовер')
meta=table.findAll('a', href=re.compile(r'/ratings/'))
i=0
for m in meta:
if i == 0:
self.story.setMetadata('rating', m.find('b').text)
self.story.setMetadata('rating', stripHTML(m))
i=1
elif i == 1:
if not "," in m.nextSibling:
@@ -192,13 +193,13 @@ class FicBookNetAdapter(BaseSiteAdapter):
if table.find('span', {'style' : 'color: green'}):
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In Progress')
self.story.setMetadata('status', 'In-Progress')
tags = table.findAll('b')
for tag in tags:
label = translit.translit(tag.text)
if 'Piersonazhi:' in label or 'Персонажи:' in label:
if 'Piersonazhi:' in label or u'Персонажи:' in label:
chars=tag.nextSibling.string.split(', ')
for char in chars:
self.story.addToList('characters',char)
@@ -207,20 +208,19 @@ class FicBookNetAdapter(BaseSiteAdapter):
summary=soup.find('span', {'class' : 'urlize'})
self.setDescription(url,summary)
#self.story.setMetadata('description', summary.text)
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
chapter = soup.find('div', {'class' : 'public_beta'})
if chapter == None:
chapter = soup.find('div', {'class' : 'public_beta_disabled'})
if None == chapter:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,chapter)
+29 -26
View File
@@ -22,7 +22,7 @@ import re
import urllib
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -39,20 +39,20 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
self.is_adult=False
# get storyId from url--url validation guarantees query correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('authorId',m.group('auth'))
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL.
self._setURL(url)
else:
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
@staticmethod
def getSiteDomain():
return 'www.fictionalley.org'
@@ -92,17 +92,17 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
raise e
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
chapterdata = data
# If chapter list page, get the first chapter to look for adult check
chapterlinklist = soup.findAll('a',{'class':'chapterlink'})
if chapterlinklist:
chapterdata = self._postFetchWithIAmOld(chapterlinklist[0]['href'])
if "Are you over seventeen years old" in chapterdata:
raise exceptions.AdultCheckRequired(self.url)
if not chapterlinklist:
# no chapter list, chapter URL: change to list link.
# second a tag inside div breadcrumbs
@@ -113,19 +113,19 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
## title's right there...
self.story.setMetadata('title',stripHTML(storya))
data = self._fetchUrl(url)
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
chapterlinklist = soup.findAll('a',{'class':'chapterlink'})
else:
## still need title from somewhere. If chapterlinklist,
## then chapterdata contains a chapter, find title the
## same way.
chapsoup = bs.BeautifulSoup(chapterdata)
chapsoup = self.make_soup(chapterdata)
storya = chapsoup.find('div',{'class':'breadcrumbs'}).findAll('a')[1]
self.story.setMetadata('title',stripHTML(storya))
del chapsoup
del chapterdata
## authorid already set.
## <h1 class="title" align="center">Just Off The Platform II by <a href="http://www.fictionalley.org/authors/drt/">DrT</a></h1>
authora=soup.find('h1',{'class':'title'}).find('a')
@@ -144,7 +144,7 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
## Go scrape the rest of the metadata from the author's page.
data = self._fetchUrl(self.story.getMetadata('authorUrl'))
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# <dl><dt><a class = "Rid story" href = "http://www.fictionalley.org/authors/aafro_man_ziegod/TMH.html">
# [Rid] The Magical Hottiez</a> by <a class = "pen_name" href = "http://www.fictionalley.org/authors/aafro_man_ziegod/">Aafro Man Ziegod</a> </small></dt>
@@ -153,15 +153,15 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
# Chaos ensues after Witch Weekly, seeking to increase readers, decides to create a boyband out of five seemingly talentless wizards: Harry Potter, Draco Malfoy, Ron Weasley, Neville Longbottom, and Oliver "Toss Your Knickers Here" Wood.<br />
# <small class = "storyinfo">Published: June 3, 2002 (between Goblet of Fire and Order of Phoenix) - Updated: June 3, 2002</small>
# </dd></dl>
storya = soup.find('a',{'href':self.story.getMetadata('storyUrl')})
storydd = storya.findNext('dd')
# Rating: PG - Spoilers: None - 2525 hits - 736 words
# Genre: Humor - Main character(s): H, R - Ships: None - Era: Multiple Eras
# Harry and Ron are back at it again! They reeeeeeally don't want to be back, because they know what's awaiting them. "VH1 Goes Inside..." is back! Why? 'Cos there are soooo many more couples left to pick on.
# Published: September 25, 2004 (between Order of Phoenix and Half-Blood Prince) - Updated: September 25, 2004
# Published: September 25, 2004 (between Order of Phoenix and Half-Blood Prince) - Updated: September 25, 2004
## change to text and regexp find.
metastr = stripHTML(storydd).replace('\n',' ').replace('\t',' ')
@@ -173,7 +173,7 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
if m:
for g in m.group(1).split(','):
self.story.addToList('genre',g)
m = re.match(r".*?Published: ([a-zA-Z]+ \d\d?, \d\d\d\d).*?",metastr)
if m:
self.story.setMetadata('datePublished',makeDate(m.group(1), "%B %d, %Y"))
@@ -185,12 +185,13 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
m = re.match(r".*? (\d+) words Genre.*?",metastr)
if m:
self.story.setMetadata('numWords', m.group(1))
for small in storydd.findAll('small'):
small.extract() ## removes the <small> tags, leaving only the summary.
storydd.name = 'div' ## change tag name else Calibre treats it oddly.
self.setDescription(url,storydd)
#self.story.setMetadata('description',stripHTML(storydd))
return
def getChapterText(self, url):
@@ -203,14 +204,16 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
# Yes, it's an evil kludge, but what can ya do? Using
# something other than div prevents soup from pairing
# our div with poor html inside the story text.
data = data.replace('<!-- headerend -->','<crazytagstringnobodywouldstumbleonaccidently id="storytext">').replace('<!-- footerstart -->','</crazytagstringnobodywouldstumbleonaccidently>')
crazy = "crazytagstringnobodywouldstumbleonaccidently"
data = data.replace('<!-- headerend -->','<'+crazy+' id="storytext">').replace('<!-- footerstart -->','</'+crazy+'>')
# problems with some stories confusing Soup. This is a nasty
# hack, but it works.
data = data[data.index("<crazytagstringnobodywouldstumbleonaccidently"):]
data = data[data.index('<'+crazy+''):]
# ditto with extra crap at the end.
data = data[:data.index('</'+crazy+'>')+len('</'+crazy+'>')]
soup = bs.BeautifulStoneSoup(data,
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(data)
body = soup.findAll('body') ## some stories use a nested body and body
## tag, in which case we don't
## need crazytagstringnobodywouldstumbleonaccidently
@@ -219,9 +222,9 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
text = body[1]
text.name='div' # force to be a div to avoid multiple body tags.
else:
text = soup.find('crazytagstringnobodywouldstumbleonaccidently', {'id' : 'storytext'})
text = soup.find(crazy, {'id' : 'storytext'})
text.name='div' # change to div tag.
if not data or not text:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
@@ -230,10 +233,10 @@ class FictionAlleyOrgSiteAdapter(BaseSiteAdapter):
# epubutils.py
for tag in text.findAll('head'):
tag.extract()
for tag in text.findAll('body') + text.findAll('html'):
tag.name = 'div'
return self.utf8FromSoup(url,text)
def getClass():
@@ -158,7 +158,7 @@ class FictionManiaTVAdapter(BaseSiteAdapter):
# While wrapping in paragraphs would be possible, it's too much work,
# I'd rather display the story 1:1 like it was found in the pre tag.
content = unicode(element)
content = content.replace('\n', '<br />')
content = content.replace('\n', '<br/>')
if self.getConfig('non_breaking_spaces'):
return content.replace(' ', '&nbsp;')
+14 -14
View File
@@ -23,7 +23,7 @@ import urllib2
import time
import json
from .. import BeautifulSoup as bs
#from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -42,7 +42,7 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL.
self._setURL("https://"+self.getSiteDomain()
+"/author/"+m.group('author')
@@ -59,7 +59,7 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
@classmethod
def getSiteExampleURLs(cls):
return "https://fictionpad.com/author/Author/stories/1234/Some-Title"
def getSiteURLPattern(self):
# http://fictionpad.com/author/Serdd/stories/4275
return r"http(s)?://(www\.)?fictionpad\.com/author/(?P<author>[^/]+)/stories/(?P<id>\d+)"
@@ -93,16 +93,16 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
params['login']))
## need to pull empty login page first to get authenticity_token
soup = bs.BeautifulSoup(self._fetchUrl(loginUrl))
soup = self.make_soup(self._fetchUrl(loginUrl))
params['authenticity_token']=soup.find('input', {'name':'authenticity_token'})['value']
data = self._postUrl(loginUrl, params)
if "Invalid email/pseudonym and password combination." in data:
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['login']))
raise exceptions.FailedToLogin(loginUrl,params['login'])
def extractChapterUrlsAndMetadata(self):
# fetch the chapter. From that we will get almost all the
@@ -116,7 +116,7 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
if "This is a mature story. Please sign in to read it." in data:
self.performLogin()
data = self._fetchUrl(url)
find = "wordyarn.config.page = "
data = data[data.index(find)+len(find):]
data = data[:data.index("</script>")]
@@ -134,14 +134,14 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
story = tables['stories'][0]
story_ver = tables['story_versions'][0]
logger.debug("story:%s"%story)
self.story.setMetadata('authorId',author['id'])
self.story.setMetadata('author',author['display_name'])
self.story.setMetadata('authorUrl','https://'+self.host+'/author/'+author['display_name']+'/stories')
self.story.setMetadata('title',story_ver['title'])
self.setDescription(url,story_ver['description'])
if not ('assets/story_versions/covers' in story_ver['profile_image_url@2x']):
self.setCoverImage(url,story_ver['profile_image_url@2x'])
@@ -165,10 +165,10 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
for i in tables['fandoms']:
self.story.addToList('category',i['name'])
for i in tables['genres']:
self.story.addToList('genre',i['name'])
for i in tables['characters']:
self.story.addToList('characters',i['name'])
@@ -179,16 +179,16 @@ class FictionPadSiteAdapter(BaseSiteAdapter):
self.chapterUrls.append((chtitle,c['body_url']))
self.story.setMetadata('numChapters',len(self.chapterUrls))
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
if not url:
data = u"<em>This chapter has no text.</em>"
else:
data = self._fetchUrl(url)
soup = bs.BeautifulSoup(u"<div id='story'>"+data+u"</div>")
soup = self.make_soup(u"<div id='story'>"+data+u"</div>")
return self.utf8FromSoup(url,soup)
def getClass():
return FictionPadSiteAdapter
+9 -9
View File
@@ -33,10 +33,10 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.story.setMetadata('siteabbrev','fw')
# get storyId from url--url validation guarantees second part is storyId
self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2])
self.username = "NoneGiven"
self.password = ""
@@ -72,8 +72,8 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
raise exceptions.FailedToLogin(url,params['username'])
return False
else:
return True
return True
def use_pagecache(self):
'''
adapters that will work with the page cache need to implement
@@ -101,7 +101,7 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
# if blocked, attempt login.
if soup.find("div",{"class":"blocked"}) or soup.find("li",{"class":"blocked"}):
if self.performLogin(url): # performLogin raises
@@ -159,7 +159,7 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
## perhaps not the most efficient way to parse this, using
## regexps for each rather than something more complex, but
## IMO, it's more readable and amenable to change.
metastr = stripHTML(str(metap)).replace('\n',' ').replace('\t',' ').replace(u'\u00a0',' ')
metastr = stripHTML(unicode(metap)).replace('\n',' ').replace('\t',' ').replace(u'\u00a0',' ')
m = re.match(r".*?Rating: (.+?) -.*?",metastr)
if m:
@@ -169,19 +169,19 @@ class FicwadComSiteAdapter(BaseSiteAdapter):
if m:
for g in m.group(1).split(','):
self.story.addToList('genre',g)
m = re.match(r".*?Characters: (.*?) -.*?",metastr)
if m:
for g in m.group(1).split(','):
if g:
self.story.addToList('characters',g)
m = re.match(r".*?Published: ([0-9-]+?) -.*?",metastr)
if m:
self.story.setMetadata('datePublished',makeDate(m.group(1), "%Y-%m-%d"))
# Updated can have more than one space after it. <shrug>
m = re.match(r".*?Updated: ([0-9-]+?) +-.*?",metastr)
m = re.match(r".*?Updated: ([0-9-]+?) +-.*?",metastr)
if m:
self.story.setMetadata('dateUpdated',makeDate(m.group(1), "%Y-%m-%d"))
+27 -24
View File
@@ -34,18 +34,18 @@ def getClass():
return FimFictionNetSiteAdapter
class FimFictionNetSiteAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.story.setMetadata('siteabbrev','fimficnet')
self.story.setMetadata('storyId', self.parsedUrl.path.split('/',)[2])
self._setURL("http://"+self.getSiteDomain()+"/story/"+self.story.getMetadata('storyId')+"/")
self.is_adult = False
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %b %Y"
@staticmethod
def getSiteDomain():
return 'www.fimfiction.net'
@@ -61,29 +61,32 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return r"https?://(www|mobile)\.fimfiction\.(net|com)/story/\d+/?.*"
def use_pagecache(self):
'''
adapters that will work with the page cache need to implement
this and change it to True.
'''
return True
def set_adult_cookie(self):
cookie = cl.Cookie(version=0, name='view_mature', value='true',
port=None, port_specified=False,
domain=self.getSiteDomain(), domain_specified=False, domain_initial_dot=False,
path='/', path_specified=True,
secure=False,
expires=time.time()+10000,
discard=False,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False)
self.get_cookiejar().set_cookie(cookie)
def doExtractChapterUrlsAndMetadata(self,get_cover=True):
if self.is_adult or self.getConfig("is_adult"):
cookie = cl.Cookie(version=0, name='view_mature', value='true',
port=None, port_specified=False,
domain=self.getSiteDomain(), domain_specified=False, domain_initial_dot=False,
path='/story', path_specified=True,
secure=False,
expires=time.time()+10000,
discard=False,
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False)
self.cookiejar.set_cookie(cookie)
self.set_adult_cookie()
##---------------------------------------------------------------------------------------------------
## Get the story's title page. Check if it exists.
@@ -99,13 +102,13 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
if "Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource" in data:
raise exceptions.StoryDoesNotExist(self.url)
if "This story has been marked as having adult content. Please click below to confirm you are of legal age to view adult material in your country." in data:
raise exceptions.AdultCheckRequired(self.url)
if self.password:
params = {}
params['password'] = self.password
@@ -277,7 +280,7 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
if not (groupList == None):
for groupName in groupList.find_all('a'):
self.story.addToList("groupsUrl", 'http://'+self.host+groupName["href"])
self.story.addToList("groupsUrl", 'http://'+self.host+groupName["href"])
self.story.addToList("groups",stripHTML(groupName).replace(',', ';'))
#sequels
@@ -286,7 +289,7 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
if header.text.startswith('Sequels'):
sequelContainer = header.parent
for sequel in sequelContainer.find_all('a', {'class':'story_link'}):
self.story.addToList("sequelsUrl", 'http://'+self.host+sequel["href"])
self.story.addToList("sequelsUrl", 'http://'+self.host+sequel["href"])
self.story.addToList("sequels", stripHTML(sequel).replace(',', ';'))
#author last login
@@ -305,7 +308,7 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
durationGroups = re.match(r"(?:[^0-9]*(\d+?)w)?[^0-9]*(?:(\d+?)d)?", lastLoginString)
lastLogin = date.today() - timedelta(days=int(durationGroups.group(2) or 0), weeks=int(durationGroups.group(1) or 0))
self.story.setMetadata("authorLastLogin", lastLogin)
#The link to the prequel is embedded in the description text, so erring
#on the side of caution and wrapping this whole thing in a try block.
#If anything goes wrong this probably wasn't a valid prequel link.
@@ -339,7 +342,7 @@ class FimFictionNetSiteAdapter(BaseSiteAdapter):
data = re.sub(r'<p([^>]*>\s*)<blockquote([^>]*>)',r'<blockquote\2<p\1',data)
data = re.sub(r'</blockquote(>\s*)</p>',r'</p\1</blockquote>',data)
return data
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
+93 -88
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -40,22 +40,22 @@ class FineStoriesComAdapter(BaseSiteAdapter):
self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
self.password = ""
self.is_adult=False
# get storyId from url
self.story.setMetadata('storyId',self.parsedUrl.path.split('/',)[2].split(':')[0])
if 'storyInfo' in self.story.getMetadata('storyId'):
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/s/storyInfo.php?id='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','fnst')
# 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.
@@ -76,7 +76,7 @@ class FineStoriesComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -89,13 +89,13 @@ class FineStoriesComAdapter(BaseSiteAdapter):
params['rememberMe'] = '1'
params['page'] = 'http://'+self.getSiteDomain()+'/'
params['submit'] = 'Login'
loginUrl = 'http://' + self.getSiteDomain() + '/login.php'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['theusername']))
d = self._fetchUrl(loginUrl, params)
if "My Account" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['theusername']))
@@ -124,20 +124,20 @@ class FineStoriesComAdapter(BaseSiteAdapter):
# 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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('a', href=re.compile(r'/s/'+self.story.getMetadata('storyId')+"$"))
a = soup.find('a', href=re.compile(r'/s/'+self.story.getMetadata('storyId')))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"/a/\w+"))
self.story.setMetadata('authorId',a['href'].split('/')[2])
@@ -156,28 +156,27 @@ class FineStoriesComAdapter(BaseSiteAdapter):
self.story.setMetadata('numChapters',len(self.chapterUrls))
# surprisingly, the detailed page does not give enough details, so go to author's page
skip=0
i=0
while i == 0:
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')+"&skip="+str(skip)))
a = asoup.findAll('td', {'class' : 'lc2'})
for lc2 in a:
if lc2.find('a')['href'] == '/s/'+self.story.getMetadata('storyId'):
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')+"&skip="+unicode(skip)))
tds = asoup.findAll('td', {'class' : 'lc2'})
for lc2 in tds:
if lc2.find('a', href=re.compile(r'/s/'+self.story.getMetadata('storyId'))):
i=1
break
if a[len(a)-1] == lc2:
if tds[len(tds)-1] == lc2:
skip=skip+10
for cat in lc2.findAll('div', {'class' : 'typediv'}):
self.story.addToList('category',cat.text)
self.story.setMetadata('size', lc2.findNext('td', {'class' : 'num'}).text)
lc4 = lc2.findNext('td', {'class' : 'lc4'})
try:
a = lc4.find('a', href=re.compile(r"/library/show_series.php\?id=\d+"))
i = a.parent.text.split('(')[1].split(')')[0]
@@ -190,33 +189,41 @@ class FineStoriesComAdapter(BaseSiteAdapter):
self.story.addToList("category",a.text)
except:
pass
for a in lc4.findAll('span', {'class' : 'help'}):
for a in lc4.findAll('span', {'class' : 'help'}) + lc4.findAll('script'):
a.extract()
self.setDescription('http://'+self.host+'/s/'+self.story.getMetadata('storyId'),lc4.text.split('[More Info')[0])
for b in lc4.findAll('b'):
label = b.text
value = b.nextSibling
if 'For Age' in label:
self.story.setMetadata('rating', value)
if 'Tags' in label:
for genre in value.split(', '):
self.story.addToList('genre',genre)
## Site uses a <script> to inject timestamp in locale plus <noscript> general version.
if 'Posted' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.split('/ (')[0]), self.dateformat))
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value.split('/ (')[0]), self.dateformat))
if 'Concluded' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value.split('/ (')[0]), self.dateformat))
if 'Updated' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value.split('/ (')[0]), self.dateformat))
value = b.find_next_sibling('noscript')
if '(' in value:
date = makeDate(stripHTML(value.split(' (')[0]), self.dateformat)
else:
date = makeDate(stripHTML(value), self.dateformat)
self.story.setMetadata('datePublished', date)
self.story.setMetadata('dateUpdated', date)
if 'Concluded' in label or 'Updated' in label:
value = b.find_next_sibling('noscript')
if '(' in value:
date = makeDate(stripHTML(value.split(' (')[0]), self.dateformat)
else:
date = makeDate(stripHTML(value), self.dateformat)
self.story.setMetadata('dateUpdated', date)
status = lc4.find('span', {'class' : 'ab'})
if status != None:
self.story.setMetadata('status', 'In-Progress')
@@ -225,64 +232,62 @@ class FineStoriesComAdapter(BaseSiteAdapter):
else:
self.story.setMetadata('status', 'Completed')
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
div = soup.find('div', {'id' : 'story'})
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('article')
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
# some big chapters are split over several pages
pager = div.find('span', {'class' : 'pager'})
if pager != None:
urls=pager.findAll('a')
urls=urls[:len(urls)-1]
for ur in urls:
soup = bs.BeautifulSoup(self._fetchUrl("http://"+self.getSiteDomain()+ur['href']),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
div1 = soup.find('div', {'id' : 'story'})
soup = self.make_soup(self._fetchUrl("http://"+self.getSiteDomain()+ur['href']))
div1 = soup.find('article')
#print("div.contents:%s"%(div.contents,))
# appending next section
last=div.findAll('p')
next=div1.find('span', {'class' : 'conTag'}).nextSibling
last[len(last)-1]=last[len(last)-1].append(next)
div.append(div1)
# removing all the left-over stuff
for a in div.findAll('span'):
a.extract()
for a in div.findAll('h1'):
a.extract()
for a in div.findAll('h2'):
a.extract()
for a in div.findAll('h3'):
a.extract()
for a in div.findAll('h4'):
a.extract()
for a in div.findAll('br'):
a.extract()
for a in div.findAll('div', {'class' : 'date'}):
a.extract()
a = div.find('form')
if a != None:
b = a.nextSibling
while b != None:
a.extract()
a=b
b=b.nextSibling
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
self.clean_chapter(div1)
#print("div.contents:%s"%(div.contents,))
for t in div1.contents:
div.append(t)
self.clean_chapter(div)
return self.utf8FromSoup(url,div)
def clean_chapter(self,art):
# discard included chapter heading.
# discard included date
# discard share block
# continued/continues
# discard next link
for tag in art.find_all('h2') + \
art.find_all('div', class_="date") + \
art.find_all('div', class_="vform") + \
art.find_all('span', class_="conTag") + \
art.find_all('h3', class_="end"):
tag.extract()
# remove pager blocks.
for pager in art.find_all('span', class_="pager"):
# remove br tags before and after pager.
#print("br list prev: %s"%len(pager.find_previous_siblings('br')))
#print("br list next: %s"%len(pager.find_next_siblings('br')))
for tag in pager.find_next_siblings('br')[:2] + pager.find_previous_siblings('br')[:2]:
tag.extract()
pager.extract()
@@ -25,10 +25,10 @@ class QuestionablequestingComAdapter(BaseXenForoForumAdapter):
def __init__(self, config, url):
BaseXenForoForumAdapter.__init__(self, config, url)
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','qq')
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -24,10 +24,10 @@ class ForumsSpacebattlesComAdapter(BaseXenForoForumAdapter):
def __init__(self, config, url):
BaseXenForoForumAdapter.__init__(self, config, url)
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','fsb')
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,31 +46,31 @@ class GrangerEnchantedCom(BaseSiteAdapter):
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])
self.section=self.parsedUrl.path.split('/',)[1]
# normalized story URL.
if "malfoymanor" in self.parsedUrl.netloc:
self._setURL('http://malfoymanor.' + self.getSiteDomain() + '/themanor/viewstory.php?sid='+self.story.getMetadata('storyId'))
self.story.addToList("category","The Manor")
else:
self._setURL('http://' + self.getSiteDomain() + '/enchant/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','gech')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%b/%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'grangerenchanted.com'
@classmethod
def getAcceptDomains(cls):
return ['grangerenchanted.com','malfoymanor.grangerenchanted.com']
@@ -90,7 +90,7 @@ class GrangerEnchantedCom(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -102,16 +102,16 @@ class GrangerEnchantedCom(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
if "enchant" in self.section:
loginUrl = 'http://grangerenchanted.com/enchant/user.php?action=login'
else:
loginUrl = 'http://malfoymanor.grangerenchanted.com/themanor/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -168,23 +168,23 @@ class GrangerEnchantedCom(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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 = self.make_soup(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',stripHTML(a))
# 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])
@@ -217,7 +217,7 @@ class GrangerEnchantedCom(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while value and not defaultGetattr(value,'class') == 'label' and '<span class="label">' not in unicode(value):
while value and 'label' not in defaultGetattr(value,'class') and '<span class="label">' not in unicode(value):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
@@ -259,7 +259,7 @@ class GrangerEnchantedCom(BaseSiteAdapter):
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))
@@ -293,17 +293,17 @@ class GrangerEnchantedCom(BaseSiteAdapter):
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'story1'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -23,7 +23,7 @@ import re
import urllib
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -40,15 +40,15 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
self.is_adult=False
# get storyId from url--url validation guarantees query is only psid=1234
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?psid='+self.story.getMetadata('storyId'))
@staticmethod
def getSiteDomain():
return 'www.harrypotterfanfiction.com'
@@ -86,10 +86,10 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
## Title
a = soup.find('a', href=re.compile(r'\?psid='+self.story.getMetadata('storyId')))
@@ -97,8 +97,8 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
## javascript:if (confirm('Please note. This story may contain adult themes. By clicking here you are stating that you are over 17. Click cancel if you do not meet this requirement.')) location = '?psid=290995'
if "This story may contain adult themes." in a['href'] and not (self.is_adult or self.getConfig("is_adult")):
raise exceptions.AdultCheckRequired(self.url)
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"viewuser.php\?showuid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -113,8 +113,8 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
tdstr = tr.findAll('td')[2].string
if tdstr and tdstr.isdigit():
words+=int(tdstr)
self.story.setMetadata('numWords',str(words))
self.story.setMetadata('numWords',unicode(words))
# Find the chapters:
tablelist = soup.find('table',{'class':'text'})
for chapter in tablelist.findAll('a', href=re.compile(r'\?chapterid=\d+')):
@@ -135,7 +135,7 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
#for center in soup.findAll('center'):
table = soup.find('table',{'class':'storymaininfo'})
if table:
metastr = stripHTML(str(table)).replace('\n',' ').replace('\t',' ')
metastr = stripHTML(unicode(table)).replace('\n',' ').replace('\t',' ')
# Rating: 12+ Story Reviews: 3
# Chapters: 3
# Characters: Andromeda, Ted, Bellatrix, R. Lestrange, Lucius, Narcissa, OC
@@ -161,18 +161,18 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
if m:
for g in m.group(1).split(','):
self.story.addToList('genre',g)
m = re.match(r".*?Characters: (.+?) Genre.*?",metastr)
if m:
for g in m.group(1).split(','):
self.story.addToList('characters',g)
m = re.match(r".*?Warnings: (.+).*?",metastr)
if m:
for w in m.group(1).split(','):
if w != 'Now Warnings':
self.story.addToList('warnings',w)
m = re.match(r".*?First Published: ([0-9\.]+).*?",metastr)
if m:
self.story.setMetadata('datePublished',makeDate(m.group(1), "%Y.%m.%d"))
@@ -186,11 +186,14 @@ class HarryPotterFanFictionComSiteAdapter(BaseSiteAdapter):
logger.debug('Getting chapter text from: %s' % url)
## most adapters use BeautifulStoneSoup here, but non-Stone
## allows nested div tags.
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
data = self._fetchUrl(url)
# remove everything after here--the site's chapters break the
# BS4 parser.
data = data[:data.index('<script type="text/javascript" src="reviewjs.js">')]
soup = self.make_soup(data)
div = soup.find('div', {'id' : 'fluidtext'})
if None == div:
+27 -28
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class HLFictionNetAdapter(BaseSiteAdapter):
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])
# 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','hlf')
# 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.
@@ -88,41 +88,41 @@ class HLFictionNetAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title and author
a = soup.find('div', {'id' : 'pagetitle'})
aut = a.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',aut['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
self.story.setMetadata('title',stripHTML(a)[:(len(a.string)-3)])
# Find the chapters:
chapters=soup.find('select')
if chapters != None:
for chapter in chapters.findAll('option'):
# just in case there's tags, like <i> in chapter titles.
# just in case there's tags, like <i> in chapter titles.
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/viewstory.php?sid='+self.story.getMetadata('storyId')+'&chapter='+chapter['value']))
else:
self.chapterUrls.append((self.story.getMetadata('title'),url))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox\s+')}):
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox')}):
a = list.find('a')
if ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
break
@@ -146,8 +146,8 @@ class HLFictionNetAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'classification':
svalue += str(value)
while 'classification' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -186,7 +186,7 @@ class HLFictionNetAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -199,7 +199,7 @@ class HLFictionNetAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -211,22 +211,21 @@ class HLFictionNetAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+19 -19
View File
@@ -49,22 +49,22 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
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])
# normalized story URL.
# XXX Most sites don't have the /eff part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/eff/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','hpfdm') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -96,11 +96,11 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
# print data
# Now go hunting for all the meta data and the chapter list.
# set first URL
a = soup.find('a', href=re.compile(r"viewstory.php\?sid=\d+"))
## href = "javascript:if (confirm('Slash/het fiction which incorporates sexual situations to a somewhat graphic degree as well as graphic violent situations. ')) location = 'viewstory.php?sid=49111&i=1'"
m = re.match(r'.*?(viewstory.php\?sid=\d+)&i=\d+.*?',a['href'])
m = re.match(r'.*?(viewstory.php\?sid=\d+)',a['href'])
self._setURL('http://'+self.host+'/eff/'+m.group(1))
if self.parsedUrl.query.split('=',)[1] != self.story.getMetadata('storyId'):
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
@@ -116,9 +116,9 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
# self.story.setMetadata('storyId', re.compile(self.getSiteURLPattern()).match(a).group('storyId'))
# 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])
@@ -133,21 +133,21 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
if "javascript:if (confirm('Slash/het fiction which incorporates sexual situations to a somewhat graphic degree as well as graphic violent situations. ')) location = 'viewstory.php?sid=%s&i=1'"%self.story.getMetadata('storyId') in authdata \
and not (self.is_adult or self.getConfig("is_adult")):
raise exceptions.AdultCheckRequired(self.url)
authsoup = self.make_soup(authdata)
reviewsa = authsoup.find('a', href=re.compile(r"reviews\.php\?sid="+self.story.getMetadata('storyId')+r".*"))
# <table><tr><td><p><b><a ...>
metablock = reviewsa.findParent("table")
#print("metablock:%s"%metablock)
## Title
titlea = metablock.find('a', href=re.compile("viewstory.php"))
#print("titlea:%s"%titlea)
if titlea == None:
raise exceptions.FailedToDownload("Story URL (%s) not found on author's page, can't use chapter URLs"%url)
self.story.setMetadata('title',stripHTML(titlea))
# Find the chapters: !!! hpfandom.net differs from every other
# eFiction site--the sid on viewstory for chapters is
# *different* for each chapter
@@ -183,7 +183,7 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
if m != None:
if m.group('rating') != None:
self.story.setMetadata('rating', m.group('rating'))
if m.group('words') != None:
self.story.setMetadata('numWords', m.group('words'))
@@ -192,7 +192,7 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
# <tr><td width="10%" valign="top">Chapters:</td><td width="40%" valign="top">4</td>
# <td width="10%" valign="top">Published:</td><td width="40%" valign="top">2010.09.29</td></tr>
@@ -225,10 +225,10 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
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))
# grab the text for an individual chapter.
def getChapterText(self, url):
@@ -239,15 +239,15 @@ class HPFandomNetAdapterAdapter(BaseSiteAdapter): # XXX
# There are, however, tables with width=100% just above and below the real text.
data = re.sub(r'<table width="100%">.*?</table>','<div name="storybody">',
data,count=1,flags=re.DOTALL)
data = re.sub(r'<table width="100%">.*?</table>','</div>',
data,count=1,flags=re.DOTALL)
soup = self.make_soup(data)
div = soup.find("div",{'name':'storybody'})
#print("\n\ndiv:%s\n\n"%div)
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from bs4.element import Comment
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/stories/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','hpffa')
# 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.
@@ -89,20 +89,20 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -131,11 +131,13 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
for labelspan in labels:
val = labelspan.nextSibling
value = unicode('')
while val and not defaultGetattr(val,'class') == 'label':
value += unicode(val)
while val and not 'label' in defaultGetattr(val,'class'):
# print("val:%s"%val)
if not isinstance(val,Comment):
value += unicode(val)
val = val.nextSibling
label = labelspan.string
#print("label:%s\nvalue:%s"%(label,value))
# print("label:%s\nvalue:%s"%(label,value))
if 'Summary' in label:
self.setDescription(url,value)
@@ -179,7 +181,7 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
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))
@@ -190,7 +192,7 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/stories/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -202,21 +204,20 @@ class HPFanficArchiveComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'story'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+25 -27
View File
@@ -22,7 +22,6 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +45,21 @@ class IkEternalNetAdapter(BaseSiteAdapter):
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])
# 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','ike')
# 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.
@@ -81,7 +80,7 @@ class IkEternalNetAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +92,13 @@ class IkEternalNetAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -141,12 +140,12 @@ class IkEternalNetAdapter(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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. ksarchive uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -169,23 +168,23 @@ class IkEternalNetAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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,selfClosingTags=('p')) #poor formatting of the paragraphs in the title page
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -221,8 +220,8 @@ class IkEternalNetAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -261,23 +260,22 @@ class IkEternalNetAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+27 -28
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
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])
# 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','ime')
# 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.
@@ -81,7 +81,7 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +93,13 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -156,23 +156,23 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -195,7 +195,7 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
labels = soup.findAll('span',{'class':'label'})
@@ -206,8 +206,8 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -246,7 +246,7 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
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))
@@ -257,7 +257,7 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -269,22 +269,21 @@ class ImagineEFicComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+38 -38
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -45,27 +45,27 @@ class InDeathNetAdapter(BaseSiteAdapter):
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 correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL.
self._setURL('http://www.' + self.getSiteDomain() + '/blog/archive/'+self.story.getMetadata('storyId')+'-'+m.group('name')+'/')
else:
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','idn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %B %Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -78,22 +78,22 @@ class InDeathNetAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
# http://www.indeath.net/blog/archive/169-ransom-in-death/
return re.escape("http://")+re.escape(self.getSiteDomain())+r"/blog/(archive/)?(?P<id>\d+)\-(?P<name>[a-z0-9\-]*)/?$"
return re.escape("http://")+re.escape(self.getSiteDomain())+r"/blog/(archive/)?(?P<id>\d+)\-(?P<name>[a-z0-9\-]*)/?$"
def getDateFromComponents(self, postmonth, postday):
ym = re.search("Entries\ in\ (?P<mon>January|February|March|April|May|June|July|August|September|October|November|December)\ (?P<year>\d{4})",postmonth)
d = re.search("(?P<day>\d{2})\ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)",postday)
ym = re.search("Entries in (?P<mon>January|February|March|April|May|June|July|August|September|October|November|December) (?P<year>\d{4})",stripHTML(postmonth))
d = re.search("(?P<day>\d{2})\ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)",stripHTML(postday))
postdate = makeDate(d.group('day')+' '+ym.group('mon')+' '+ym.group('year'),self.dateformat)
return postdate
def getAuthorData(self):
mainUrl = self.url.replace("/archive","")
try:
maindata = self._fetchUrl(mainUrl)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.meta)
@@ -101,45 +101,45 @@ class InDeathNetAdapter(BaseSiteAdapter):
raise e
# use BeautifulSoup HTML parser to make everything easier to find.
mainsoup = bs.BeautifulSoup(maindata)
mainsoup = self.make_soup(maindata)
# find first entry
e = mainsoup.find('div',{'class':"entry"})
# get post author as author
d = e.find('div',{'class':"desc"})
a = d.find('strong')
self.story.setMetadata('author',a.contents[0].string.strip())
# Don't seem to be able to get author pages anymore
self.story.setMetadata('authorUrl','http://www.indeath.net/')
self.story.setMetadata('authorId','0')
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
url = self.url
try:
data = self._fetchUrl(url)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.meta)
else:
raise e
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# Now go hunting for all the meta data and the chapter list.
## Title
h = soup.find('a', id="blog_title")
t = h.find('span')
self.story.setMetadata('title',stripHTML(t.contents[0]).strip())
s = t.find('div')
s = t.find('div')
if s != None:
self.setDescription(url,s)
@@ -151,20 +151,20 @@ class InDeathNetAdapter(BaseSiteAdapter):
#reverse the list since newest at the top
chapters.reverse()
# Get date published & updated from first & last entries
posttable=soup.find('div', id="main_column")
postmonths=posttable.findAll('th', text=re.compile(r'Entries\ in\ '))
postmonths.reverse()
postdates=posttable.findAll('span', _class="desc", text=re.compile('\d{2}\ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'))
postdates=posttable.findAll('span', class_="desc")
postdates.reverse()
self.story.setMetadata('datePublished',self.getDateFromComponents(postmonths[0],postdates[0]))
self.story.setMetadata('dateUpdated',self.getDateFromComponents(postmonths[len(postmonths)-1],postdates[len(postdates)-1]))
# Process List of Chapters
# Process List of Chapters
self.story.setMetadata('numChapters',len(chapters))
logger.debug("numChapters: (%s)"%self.story.getMetadata('numChapters'))
for x in range(0,len(chapters)):
@@ -181,18 +181,18 @@ class InDeathNetAdapter(BaseSiteAdapter):
chaptertitle = ct
self.chapterUrls.append((chaptertitle,chapter['href']))
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
#chapter=bs.BeautifulSoup('<div class="story"></div>')
#chapter=self.make_soup('<div class="story"></div>')
data = self._fetchUrl(url)
soup = bs.BeautifulSoup(data,selfClosingTags=('br','hr','span','center'))
soup = self.make_soup(data)
chapter = soup.find("div", "entry_content")
if None == chapter:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+23 -24
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -50,22 +50,22 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
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','ksa') # 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
@classmethod
def getAcceptDomains(cls):
return ['www.ksarchive.com','ksarchive.com']
@@ -115,12 +115,12 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. ksarchive uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -143,20 +143,20 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a)) # title's inside a <b> tag.
@@ -194,8 +194,8 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
# poor HTML(unclosed <p> for one) can cause run on
# over the next label.
if '<span class="label">' in svalue:
@@ -281,7 +281,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -294,7 +294,7 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
i=1
for a in storyas:
@@ -305,20 +305,19 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
data = self._fetchUrl(url)
soup = bs.BeautifulStoneSoup(data,
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(data)
div = soup.find('div', {'id' : 'story'})
if None == div:
@@ -326,5 +325,5 @@ class KSArchiveComAdapter(BaseSiteAdapter): # XXX
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Database error on the site reported!" % url)
else:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+20 -20
View File
@@ -23,7 +23,7 @@ import urllib2
import urlparse
import time
from .. import BeautifulSoup as bs
from bs4.element import Comment
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -98,7 +98,7 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
def extractChapterUrlsAndMetadata(self):
"""
NOTE: Some stories can have versions,
NOTE: Some stories can have versions,
e.g. /my-story-ch-05-version-10
NOTE: If two stories share the same title, a running index is added,
e.g.: /my-story-ch-02-1
@@ -121,9 +121,9 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
logger.debug("Chapter/Story URL: <%s> " % self.url)
try:
data1 = self._fetchUrl(self.url)
soup1 = bs.BeautifulSoup(data1)
soup1 = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, bs.Comment))]
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, Comment))]
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
@@ -142,9 +142,9 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
# get the author page
try:
dataAuth = self._fetchUrl(authorurl)
soupAuth = bs.BeautifulSoup(dataAuth)
soupAuth = self.make_soup(dataAuth)
#strip comments from soup
[comment.extract() for comment in soupAuth.findAll(text=lambda text:isinstance(text, bs.Comment))]
[comment.extract() for comment in soupAuth.findAll(text=lambda text:isinstance(text, Comment))]
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(authorurl)
@@ -158,7 +158,7 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
if storyLink is not None:
urlTr = storyLink.parent.parent
if urlTr['class'] == "sl":
if "sl" in urlTr['class']:
isSingleStory = False
else:
isSingleStory = True
@@ -175,7 +175,7 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
self.chapterUrls = [(storyLink.text, self.url)]
else:
seriesTr = urlTr.previousSibling
while seriesTr['class'] != 'ser-ttl':
while 'ser-ttl' not in seriesTr['class']:
seriesTr = seriesTr.previousSibling
m = re.match("^(?P<title>.*?):\s(?P<numChapters>\d+)\sPart\sSeries$", seriesTr.find("strong").text)
self.story.setMetadata('title', m.group('title'))
@@ -185,11 +185,11 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
self.chapterUrls = []
dates = []
descriptions = []
while chapterTr is not None and chapterTr['class'] == 'sl':
while chapterTr is not None and 'sl' in chapterTr['class']:
descriptions.append(chapterTr.findAll("td")[1].text)
chapterLink = chapterTr.find("td", "fc").find("a")
if not chapterLink["href"].startswith('http'):
chapterLink["href"] = "http:" + chapterLink["href"]
chapterLink["href"] = "http:" + chapterLink["href"]
self.chapterUrls.append((chapterLink.text, chapterLink["href"]))
self.story.addToList('eroticatags', chapterTr.findAll("td")[2].text)
dates.append(makeDate(chapterTr.findAll('td')[-1].text, self.dateformat))
@@ -207,9 +207,9 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
self._setURL(self.chapterUrls[0][1])
self.story.setMetadata('numChapters', len(self.chapterUrls))
# set storyId to 'title-author' to avoid duplicates
# self.story.setMetadata('storyId',
# self.story.setMetadata('storyId',
# re.sub("[^a-z0-9]", "", self.story.getMetadata('title').lower())
# + "-"
# + re.sub("[^a-z0-9]", "", self.story.getMetadata('author').lower()))
@@ -223,37 +223,37 @@ class LiteroticaSiteAdapter(BaseSiteAdapter):
# done by changing tag name, it causes problems with nested
# <p> tags.
data1 = data1.replace('<div class="b-story-body-x x-r15"><div><p>','<div class="b-story-body-x x-r15"><div>')
soup1 = bs.BeautifulSoup(data1)
soup1 = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, bs.Comment))]
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, Comment))]
# get story text
story1 = soup1.find('div', 'b-story-body-x').div
#print("story1:%s"%story1)
# story1.name='div'
story1.append('<br />')
story1.append(soup1.new_tag('br'))
storytext = self.utf8FromSoup(url,story1)
# find num pages
pgs = int(soup1.find("span", "b-pager-caption-t r-d45").string.split(' ')[0])
logger.debug("pages: "+str(pgs))
logger.debug("pages: "+unicode(pgs))
# get all the pages
for i in xrange(2, pgs+1):
try:
logger.debug("fetching page "+str(i))
logger.debug("fetching page "+unicode(i))
time.sleep(0.5)
data2 = self._fetchUrl(url, {'page': i})
# brute force approach to replace the wrapping <p> tag. If
# done by changing tag name, it causes problems with nested
# <p> tags.
data2 = data2.replace('<div class="b-story-body-x x-r15"><div><p>','<div class="b-story-body-x x-r15"><div>')
soup2 = bs.BeautifulSoup(data2)
[comment.extract() for comment in soup2.findAll(text=lambda text:isinstance(text, bs.Comment))]
soup2 = self.make_soup(data2)
[comment.extract() for comment in soup2.findAll(text=lambda text:isinstance(text, Comment))]
story2 = soup2.find('div', 'b-story-body-x').div
# story2.name='div'
story2.append('<br />')
story2.append(soup2.new_tag('br'))
storytext += self.utf8FromSoup(url,story2)
except urllib2.HTTPError, e:
if e.code == 404:
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
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])
# 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','lsph')
# 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.
@@ -104,21 +104,21 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
# the title search to troubleshoot.
if "Age Consent Required" in data:
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -155,8 +155,8 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
while value != None:
val = value
value = value.previousSibling
while not defaultGetattr(val,'class') == 'label':
svalue += str(val)
while 'label' not in defaultGetattr(val,'class'):
svalue += unicode(val)
val = val.nextSibling
self.setDescription(url,svalue)
@@ -195,7 +195,7 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.split(' -')[0]), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -208,7 +208,7 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -217,22 +217,21 @@ class LumosSycophantHexComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+1 -2
View File
@@ -218,7 +218,7 @@ class MassEffect2InAdapter(BaseSiteAdapter):
self.story.setMetadata('status', 'In Progress' if storyInProgress else 'Completed')
self.story.setMetadata('datePublished', datePublished)
self.story.setMetadata('dateUpdated', dateUpdated)
self.story.setMetadata('numWords', str(wordCount))
self.story.setMetadata('numWords', unicode(wordCount))
self.story.setMetadata('numChapters', chapterCount)
# Site-specific metadata.
@@ -554,7 +554,6 @@ class Chapter(object):
def _parseRatingFromImage(self, element):
"""Given an image element, try to parse story rating from it."""
# Although deprecated, `has_key()' is required here.
if not element.has_attr('src'):
return
source = element['src']
+189
View File
@@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-
# Copyright 2013 Fanficdownloader team, 2015 FanFicFare team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import time
import logging
logger = logging.getLogger(__name__)
import re
import urllib2
import urlparse
import time
import os
from bs4.element import Comment
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
from base_adapter import BaseSiteAdapter, makeDate
class MCStoriesComSiteAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
self.decode = ["utf8",
"Windows-1252"] # 1252 is a superset of iso-8859-1.
# Most sites that claim to be
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
self.story.setMetadata('siteabbrev','mcstories')
# Normalize story URL to the chapter index page (.../index.html)
url = re.sub("/([a-zA-Z0-9_-]+.html)?$", "/index.html", url)
# Extract story ID from base URL, http://mcstories.com/STORY_ID/index.html
storyId = self.parsedUrl.path.split('/',)[1]
self.story.setMetadata('storyId', storyId)
## set url
self._setURL(url)
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = '%d %B %Y'
@staticmethod
def getSiteDomain():
return 'mcstories.com'
@classmethod
def getAcceptDomains(cls):
return ['mcstories.com',
'www.mcstories.com']
@classmethod
def getSiteExampleURLs(cls):
return "http://mcstories.com/StoryTitle http://mcstories.com/StoryTitle/index.html http://mcstories.com/StoryTitle/StoryTitle1.html"
def getSiteURLPattern(self):
return r"https?://(www\.)?mcstories\.com/([a-zA-Z0-9_-]+)/"
def extractChapterUrlsAndMetadata(self):
"""
Chapters are located at /StoryName/StoryName.html (for single-chapter
stories), or /StoryName/StoryName#.html for multiple chapters (# is a
non-padded incrementing number, like StoryName1, StoryName2.html, ...,
StoryName10.html)
The story metadata page is at /StoryName/index.html , including a list
of chapters.
"""
if not (self.is_adult or self.getConfig("is_adult")):
raise exceptions.AdultCheckRequired(self.url)
try:
data1 = self._fetchUrl(self.url)
soup1 = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, Comment))]
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
# Extract metadata
title = soup1.find('h3', class_='title')
self.story.setMetadata('title', title.text)
# Author
author = soup1.find('h3', class_='byline').a
authorurl = urlparse.urljoin(self.url, author['href'])
self.story.setMetadata('author', author.text)
self.story.setMetadata('authorUrl', authorurl)
authorid = os.path.splitext(os.path.basename(authorurl))[0]
self.story.setMetadata('authorId', authorid)
# Description
synopsis = soup1.find('section', class_='synopsis')
description = "\n\n".join([p.text for p in synopsis.findAll('p')])
self.story.setMetadata('description', description)
# Tags
codesDiv = soup1.find('div', class_="storyCodes")
for a in codesDiv.findAll('a'):
self.story.addToList('eroticatags', a.text)
# Publish and update dates
publishdate = None
updatedate = None
datelines = soup1.findAll('h3', class_='dateline')
for dateline in datelines:
if dateline.text.startswith('Added '):
publishdate = makeDate(dateline.text, "Added " + self.dateformat)
elif dateline.text.startswith('Updated '):
updatedate = makeDate(dateline.text, "Updated " + self.dateformat)
if publishdate is not None: self.story.setMetadata('datePublished', publishdate)
if updatedate is not None: self.story.setMetadata('dateUpdated', updatedate)
# Get chapter URLs
self.chapterUrls = []
chapterTable = soup1.find('table', class_='index')
if chapterTable is not None:
# Multi-chapter story
chapterRows = chapterTable.findAll('tr')
for row in chapterRows:
chapterCell = row.td
if chapterCell is not None:
link = chapterCell.a
chapterTitle = link.text
chapterUrl = urlparse.urljoin(self.url, link['href'])
self.chapterUrls.append((chapterTitle, chapterUrl))
else:
# Single chapter
chapterDiv = soup1.find('div', class_='chapter')
chapterTitle = chapterDiv.a.text
chapterUrl = urlparse.urljoin(self.url, chapterDiv.a['href'])
self.chapterUrls = [(chapterTitle, chapterUrl)]
self.story.setMetadata('numChapters', len(self.chapterUrls))
logger.debug("Story: <%s>", self.story)
return
def getChapterText(self, url):
"""
Clean up a mcstories chapter page.
All content is in article#mcstories, with chapter headers in h3
"""
logger.debug('Getting chapter text from <%s>' % url)
data1 = self._fetchUrl(url)
soup1 = self.make_soup(data1)
#strip comments from soup
[comment.extract() for comment in soup1.findAll(text=lambda text:isinstance(text, Comment))]
# get story text
story1 = soup1.find('article', id='mcstories')
# Remove duplicate name and author headers
[h3.extract() for h3 in story1.findAll('h3')]
storytext = self.utf8FromSoup(url, story1)
return storytext
def getClass():
return MCStoriesComSiteAdapter
+46 -43
View File
@@ -37,10 +37,11 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
# Most sites that claim to be
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
# get storyId from url--url validation guarantees query correct
m = re.match(self.getSiteURLPattern(),url)
urltitle='urltitle'
cattitle='cattitle'
if m:
if m.group('id1'):
self.story.setMetadata('storyId',m.group('id1'))
@@ -50,29 +51,37 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
urltitle=m.group('urltitle2')
elif m.group('id3'):
self.story.setMetadata('storyId',m.group('id3'))
elif m.group('id4'):
self.story.setMetadata('storyId',m.group('id4'))
cattitle=m.group('cattitle4')
urltitle=m.group('urltitle4')
elif m.group('id5'):
self.story.setMetadata('storyId',m.group('id5'))
cattitle=m.group('cattitle5')
urltitle=m.group('urltitle5')
else:
raise InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/fanfic/s/'+urltitle+'/'+self.story.getMetadata('storyId'))
self._setURL('http://' + self.getSiteDomain() + '/fanfic/s/'+cattitle+'/'+urltitle+'/'+self.story.getMetadata('storyId'))
else:
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%B %d, %Y %H:%M"
@staticmethod
def getSiteDomain():
return 'www.mediaminer.org'
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/fanfic/s/story-title/123456 http://"+cls.getSiteDomain()+"/fanfic/c/story-title/chapter-title/123456/987612"
return "http://"+cls.getSiteDomain()+"/fanfic/s/category-name/story-title/123456 http://"+cls.getSiteDomain()+"/fanfic/c/category-name/story-title/123456/987612"
def getSiteURLPattern(self):
## old urls
@@ -82,11 +91,16 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
## http://www.mediaminer.org/fanfic/c/ghosts-from-the-past/chapter-2/72/174
## http://www.mediaminer.org/fanfic/s/robtech-final-missions/61553
## http://www.mediaminer.org/fanfic/c/robtech-final-missions/robotech-final-missions-oneshot/61553/189830
## even newer urls
## http://www.mediaminer.org/fanfic/s/gundam-wing-fan-fiction/the-preventer-operatives/171000
## http://www.mediaminer.org/fanfic/c/gundam-wing-fan-fiction/the-preventer-operatives/171000/608822
return re.escape("http://"+self.getSiteDomain())+r"/fanfic/"+\
r"((s/(?P<urltitle1>[^/]+)/(?P<id1>\d+))|"+\
r"((s/(?P<cattitle4>[^/]+)/(?P<urltitle4>[^/]+)/(?P<id4>\d+))|"+\
r"((c/(?P<cattitle5>[^/]+)/(?P<urltitle5>[^/]+)/(?P<id5>\d+))/\d+)|"+\
r"(s/(?P<urltitle1>[^/]+)/(?P<id1>\d+))|"+\
r"((c/(?P<urltitle2>[^/]+)/[^/]+/(?P<id2>\d+))/\d+)|"+\
r"(view_st\.php/(?P<id3>\d+)))"
# Override stripURLParameters so the id parameter won't get stripped
@classmethod
def stripURLParameters(cls, url):
@@ -109,62 +123,51 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
# use BeautifulSoup HTML parser to make everything easier to find.
soup = self.make_soup(data)
# [ A - All Readers ], strip '[' ']'
## title:
## <h1 id="post-title">A, A' Fan Fiction &#10095; Mmmmm</h1>
titletext = stripHTML(soup.find("h1",{"id":"post-title"}))
titletext = titletext[titletext.index(u'')+2:]
# print("title:(%s)"%titletext)
self.story.setMetadata('title',titletext)
# [ A - All Readers ], strip '[ ' ' ]'
## Above title because we remove the smtxt font to get title.
smtxt = soup.find("h3",{"id":"post-rating"})
smtxt = soup.find("div",{"id":"post-rating"})
if not smtxt:
logger.error("can't find rating")
raise exceptions.StoryDoesNotExist(self.url)
else:
rating = smtxt.string[1:-1]
rating = smtxt.string[2:-2]
self.story.setMetadata('rating',rating)
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"/fanfic/src.php/u/\d+"))
a = soup.find('a', href=re.compile(r"/user_info.php/\d+"))
self.story.setMetadata('authorId',a['href'].split('/')[-1])
self.story.setMetadata('authorUrl','http://'+self.host+a['href'])
self.story.setMetadata('author',a.string)
## Title - Good grief. Title varies by chaptered, 1chapter and 'type=one shot'--and even 'one-shot's can have titled chapter.
## But, if colspan=2, there's no chapter title.
## <td class="ffh">Atmosphere: Chapter 1</b> <font class="smtxt">[ P - Pre-Teen ]</font></td>
## <td colspan=2 class="ffh">Hearts of Ice <font class="smtxt">[ P - Pre-Teen ]</font></td>
## <td colspan=2 class="ffh">Suzaku no Princess <font class="smtxt">[ P - Pre-Teen ]</font></td>
## <td class="ffh">The Kraut, The Bartender, and The Drunkard: Chapter 1</b> <font class="smtxt">[ P - Pre-Teen ]</font></td>
## <td class="ffh">Betrayal and Justice: A Cold Heart</b> <font size="-1">( Chapter 1 )</font> <font class="smtxt">[ A - All Readers ]</font></td>
## <td class="ffh">Question and Answer: Question and Answer</b> <font size="-1">( One-Shot )</font> <font class="smtxt">[ A - All Readers ]</font></td>
# title = soup.find('td',{'class':'ffh'})
# for font in title.findAll('font'):
# font.extract() # removes 'font' tags from inside the td.
# if title.has_attr('colspan'):
# titlet = stripHTML(title)
# else:
# ## No colspan, it's part chapter title--even if it's a one-shot.
# titlet = ':'.join(stripHTML(title).split(':')[:-1]) # strip trailing 'Chapter X' or chapter title
self.story.setMetadata('title',stripHTML(soup.find('h1',{'id':'post-title'})))
# save date from first for later.
firstdate=None
# Find the chapters - one-shot now have chapter list, too.
chap_p = soup.find('p',{'style':'margin-left:10px;'})
for (atag,aurl,name) in [ (x,x['href'],stripHTML(x)) for x in chap_p.find_all('a') ]:
self.chapterUrls.append((name,'http://'+self.host+aurl))
self.story.setMetadata('numChapters',len(self.chapterUrls))
# category
# <a href="/fanfic/src.php/a/567">Ranma 1/2</a>
for a in soup.findAll('a',href=re.compile(r"^/fanfic/src.php/a/")):
for a in soup.findAll('a',href=re.compile(r"^/fanfic/a/")):
self.story.addToList('category',a.string)
# genre
# <a href="/fanfic/src.php/g/567">Ranma 1/2</a>
for a in soup.findAll('a',href=re.compile(r"^/fanfic/src.php/g/")):
self.story.addToList('genre',a.string)
metastr = stripHTML(soup.find("div",{"class":"post-meta"}))
# Latest Revision: February 07, 2015 15:21 PST
m = re.match(r".*?(?:Latest Revision|Uploaded On): ([a-zA-Z]+ \d\d, \d\d\d\d \d\d:\d\d)",metastr)
if m:
@@ -178,9 +181,9 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
m = re.match(r".*?\| Words: (\d+) \|",metastr)
if m:
self.story.setMetadata('numWords', m.group(1))
# Summary: ....
m = re.match(r".*?Summary: (.*)$",metastr)
m = re.match(r".*?Summary: (.*)$",metastr)
if m:
self.setDescription(url, m.group(1))
#self.story.setMetadata('description', m.group(1))
@@ -210,11 +213,11 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
date = makeDate(m.group(1), self.dateformat)
if not self.story.getMetadataRaw('datePublished') or date < self.story.getMetadataRaw('datePublished'):
self.story.setMetadata('datePublished', date)
chapter = soup.find('div',{'id':'fanfic-text'})
return self.utf8FromSoup(url,chapter)
# chapter=self.make_soup('<div class="story"></div>').find('div')
# if None == header:
@@ -230,7 +233,7 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
# del div['style']
# del div['align']
# return self.utf8FromSoup(url,chapter)
# else:
# logger.debug('Using kludgey text find for older mediaminer story.')
# ## Some older mediaminer stories are unparsable with BeautifulSoup.
@@ -244,9 +247,9 @@ class MediaMinerOrgSiteAdapter(BaseSiteAdapter):
# soup.findAll('div',{'class':'footer smtxt'}) + \
# soup.findAll('table',{'class':'tbbrdr'}):
# tag.extract() # remove tag from soup.
# return self.utf8FromSoup(url,soup)
def getClass():
return MediaMinerOrgSiteAdapter
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
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])
# 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','mrfd')
# 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.
@@ -81,7 +81,7 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +93,13 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -156,23 +156,23 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -205,8 +205,8 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -226,7 +226,7 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
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=1'))
for ship in ships:
@@ -250,7 +250,7 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
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))
@@ -261,7 +261,7 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -273,22 +273,21 @@ class MerlinFicDtwinsCoUk(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -50,22 +50,22 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
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','mw') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -111,12 +111,12 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. nfacommunity uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -139,20 +139,20 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a)) # title's inside a <b> tag.
@@ -189,8 +189,8 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -241,7 +241,7 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -254,7 +254,7 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -265,20 +265,19 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
data = self._fetchUrl(url)
soup = bs.BeautifulStoneSoup(data,
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(data)
div = soup.find('div', {'id' : 'story'})
if None == div:
@@ -286,5 +285,5 @@ class MidnightwhispersCaAdapter(BaseSiteAdapter): # XXX
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Database error on the site reported!" % url)
else:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+28 -39
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -50,21 +50,21 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
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])
# 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','mgln') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain.
@@ -89,7 +89,7 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -101,13 +101,13 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login&sid='+self.story.getMetadata('storyId')
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -151,12 +151,12 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. nfacommunity uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -179,23 +179,23 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -217,8 +217,8 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
end='<span class="label">Rated:</span>'
summarydata = data[data.index(start)+len(start):data.index(end)]
#print("summarydata:\n%s\n"%summarydata)
self.setDescription(url,bs.BeautifulSoup(summarydata))
self.setDescription(url,self.make_soup(summarydata))
# eFiction sites don't help us out a lot with their meta data
# formating, so it's a little ugly.
@@ -235,16 +235,6 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
value = labelspan.nextSibling
label = labelspan.string
# not good enough--poorly formated summary html will break it.
# 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)
@@ -291,7 +281,7 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -304,7 +294,7 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
i=1
for a in storyas:
@@ -315,22 +305,21 @@ class MuggleNetComAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,32 +46,32 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
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 storyid=1234
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?storyid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ntlb')
# 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():
return 'national-library.net'
@classmethod
def getAcceptDomains(cls):
return ['www.national-library.net','national-library.net']
@classmethod
def getSiteExampleURLs(cls):
# ONLY the stories archived on or after June 17, 2006 and that are hosted on the website:
# ONLY the stories archived on or after June 17, 2006 and that are hosted on the website:
return "http://"+cls.getSiteDomain()+"/viewstory.php?storyid=1234"
def getSiteURLPattern(self):
@@ -93,20 +93,20 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('h1')
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"authorresults.php\?author=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -130,7 +130,7 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
for x in range(2,len(labels)):
value = labels[x].nextSibling
label = labels[x].string
if 'Summary' in label:
self.setDescription(url,value)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -152,43 +152,42 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
if 'Character' in label:
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)
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Series' in label:
self.setSeries(stripHTML(value.nextSibling), value.nextSibling.nextSibling.string[2:])
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+value.nextSibling['href'])
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
story=asoup.find('a', href=re.compile(r'viewstory.php\?storyid='+self.story.getMetadata('storyId')))
a=story.findNext(text=re.compile('Genre')).parent.nextSibling.string.split(', ')
for genre in a:
self.story.setMetadata('genre', genre)
a=story.findNext(text=re.compile('Archived'))
self.story.setMetadata('datePublished', makeDate(stripHTML(a.parent.nextSibling), self.dateformat))
self.story.setMetadata('dateUpdated', makeDate(stripHTML(a.parent.nextSibling), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div')
# bit messy since higly inconsistent
for p in soup.findAll('p', {'align' : 'center'}):
p.extract()
@@ -209,5 +208,5 @@ class NationalLibraryNetAdapter(BaseSiteAdapter):
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+27 -28
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,25 +46,25 @@ class NCISFicComAdapter(BaseSiteAdapter):
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 storyid=1234
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?storyid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ncisf')
# 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():
return 'ncisfic.com'
@classmethod
def getAcceptDomains(cls):
return ['www.ncisfic.com','ncisfic.com']
@@ -92,20 +92,20 @@ class NCISFicComAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('h1')
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('a', href=re.compile(r"authorresults.php\?author=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -129,7 +129,7 @@ class NCISFicComAdapter(BaseSiteAdapter):
for x in range(2,len(labels)):
value = labels[x].nextSibling
label = labels[x].string
if 'Summary' in label:
self.setDescription(url,value)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -151,50 +151,49 @@ class NCISFicComAdapter(BaseSiteAdapter):
if 'Character' in label:
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)
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Series' in label:
if "No Series" not in value.nextSibling.string:
self.setSeries(stripHTML(value.nextSibling), value.nextSibling.nextSibling.string[2:])
self.story.setMetadata('seriesUrl','http://'+self.host+'/'+value.nextSibling['href'])
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
story=asoup.find('a', href=re.compile(r'viewstory.php\?storyid='+self.story.getMetadata('storyId')))
a=story.findNext('font')
if 'Complete' in a.string:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
a=story.findNext(text=re.compile('Genre')).parent.nextSibling.string.split(', ')
for genre in a:
self.story.setMetadata('genre', genre)
a=story.findNext(text=re.compile('Archived'))
self.story.setMetadata('datePublished', makeDate(stripHTML(a.parent.nextSibling), self.dateformat))
self.story.setMetadata('dateUpdated', makeDate(stripHTML(a.parent.nextSibling), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div')
# bit messy since higly inconsistent
for p in soup.findAll('p', {'align' : 'center'}):
p.extract()
@@ -215,5 +214,5 @@ class NCISFicComAdapter(BaseSiteAdapter):
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+22 -23
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -45,22 +45,22 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
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])
# normalized story URL.
self._setURL("http://"+self.getSiteDomain()\
+"/chapters.php?stid="+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ncisfn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -69,7 +69,7 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
## Changed from www.ncisfiction.com to www.ncisfiction.net Oct
## 2012 due to the ncisfiction.com domain expiring. Still accept
## .com domains for existing updates, etc.
@classmethod
def getAcceptDomains(cls):
return ['www.ncisfiction.net','www.ncisfiction.com']
@@ -80,7 +80,7 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return r'http://www\.ncisfiction\.(net|com)/(chapters|story)?.php\?stid=\d+'
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -98,24 +98,24 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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.BeautifulStoneSoup(data)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title and author
a = soup.find('div', {'class' : 'main_title'})
aut = a.find('a')
self.story.setMetadata('authorId',aut['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
self.story.setMetadata('title',stripHTML(a)[:len(stripHTML(a))-2])
@@ -138,9 +138,9 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
# formating, so it's a little ugly.
info = soup.find('table', {'class' : 'story_info'})
# no convenient way to calculate word count as it is logged differently for stories with and without series
labels = info.findAll('tr')
for tr in labels:
value = tr.find('td')
@@ -182,7 +182,7 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
self.story.setMetadata('status', 'In-Progress')
else:
self.story.setMetadata('status', 'Completed')
try:
# Find Series name from series URL.
a = soup.find('div',{'class' : 'sub_header'})
@@ -193,18 +193,17 @@ class NCISFictionNetAdapter(BaseSiteAdapter):
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'class' : 'story_text'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+24 -25
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -50,22 +50,22 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
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','nfa') # 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
@classmethod
def getAcceptDomains(cls):
return ['www.nfacommunity.com','nfacommunity.com']
@@ -115,12 +115,12 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. nfacommunity uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -143,22 +143,22 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -193,8 +193,8 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -245,7 +245,7 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -258,7 +258,7 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
i=1
for a in storyas:
@@ -269,22 +269,21 @@ class NfaCommunityComAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -29,11 +29,11 @@ class NHAMagicalWorldsUsAdapter(BaseEfictionAdapter):
@classmethod
def getSiteAbbrev(self):
return 'nha'
@classmethod
def getDateFormat(self):
return "%d/%m/%y"
return "%d/%m/%y"
def getClass():
return NHAMagicalWorldsUsAdapter
+30 -32
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class NickAndGregNetAdapter(BaseSiteAdapter):
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/desert_archive/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','nag')
# 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.
@@ -70,14 +70,14 @@ class NickAndGregNetAdapter(BaseSiteAdapter):
@classmethod
def getAcceptDomains(cls):
return ['www.nickngreg.nl','www.nickandgreg.net']
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/desert_archive/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return "http://("+self.getSiteDomain()+"|www.nickandgreg.net)"+re.escape("/desert_archive/viewstory.php?sid=")+r"\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -94,12 +94,12 @@ class NickAndGregNetAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
@@ -107,7 +107,7 @@ class NickAndGregNetAdapter(BaseSiteAdapter):
## Title
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# 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])
@@ -121,60 +121,58 @@ class NickAndGregNetAdapter(BaseSiteAdapter):
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/desert_archive/'+chapter['value']))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for div in asoup.findAll('td', {'class' : 'tblborder6'}):
a = div.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
if a != None:
break
self.setDescription(url,div.find('br').nextSibling)
a=div.text.split('Rating:')
if len(a) == 2: self.story.setMetadata('rating', a[1].split(' -')[0])
a=div.text.split('Characters:')
if len(a) == 2:
if len(a) == 2:
for char in a[1].split(' -')[0].split(', '):
self.story.addToList('characters',char)
a=div.text.split('Genres:')
if len(a) == 2:
if len(a) == 2:
for genre in a[1].split(' -')[0].split(', '):
self.story.addToList('genre',genre)
a=div.text.split('Warnings:')
if len(a) == 2:
if len(a) == 2:
for warn in a[1].split(' -')[0].split(', '):
if 'none' not in warn:
self.story.addToList('warnings',warn)
a=div.text.split('Completed:')
if len(a) ==2:
if 'Yes' in a[1]:
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
a=div.text.split('Published:')
if len(a) == 2: self.story.setMetadata('datePublished', makeDate(stripHTML(a[1].split(' -')[0]), self.dateformat))
a=div.text.split('Updated:')
if len(a) == 2: self.story.setMetadata('dateUpdated', makeDate(stripHTML(a[1].split(' -')[0]), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
# wrap a div around it.
divsoup = bs.BeautifulStoneSoup('<div class="story"></div>',
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
divsoup = self.make_soup('<div class="story"></div>')
div = divsoup.find('div')
div.append(soup.find('table', {'class' : 'tblborder6'}))
@@ -32,24 +32,24 @@ class NineLivesAdapter(BaseEfictionAdapter):
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/viewstory.php?sid=1234"
@classmethod
def getSiteURLPattern(self):
return "http://("+self.getSiteDomain()+"|ninelives.dark-solace.org)"+re.escape("/viewstory.php?sid=")+r"(?P<storyId>\d+)$"
@classmethod
def getConfigSections(cls):
"Only needs to be overriden if has additional ini sections."
return ['base_efiction','ninelives.dark-solace.org',cls.getSiteDomain()]
@classmethod
def getSiteAbbrev(self):
return '9lvs'
@classmethod
def getDateFormat(self):
return "%B %d, %Y"
return "%B %d, %Y"
def getClass():
return NineLivesAdapter
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
import re
import urllib2
import urlparse
from .. import BeautifulSoup
from bs4.element import Tag
from base_adapter import BaseSiteAdapter, makeDate
from .. import exceptions
@@ -52,7 +54,7 @@ class NocturnalLightNetAdapter(BaseSiteAdapter):
else:
data = self._fetchUrl(url, parameters)
return BeautifulSoup.BeautifulSoup(data)
return self.make_soup(data)
@staticmethod
def getSiteDomain():
@@ -111,7 +113,7 @@ class NocturnalLightNetAdapter(BaseSiteAdapter):
for b_tag in listbox('b'):
key = b_tag.string.strip(':')
try:
value = b_tag.nextSibling.string.replace('&bull;', '').strip(': ')
value = b_tag.nextSibling.string.replace('&bull;', '').replace(u'','').strip(': ')
# This can happen with some fancy markup in the summary. Just
# ignore this error and set value to None, the summary parsing
# takes care of this
@@ -123,7 +125,7 @@ class NocturnalLightNetAdapter(BaseSiteAdapter):
keep_summary_html = self.getConfig('keep_summary_html')
for sibling in _yield_next_siblings(b_tag):
if isinstance(sibling, BeautifulSoup.Tag):
if isinstance(sibling, Tag):
if sibling.name == 'b' and sibling.findPreviousSibling().name == 'br':
break
@@ -149,6 +151,7 @@ class NocturnalLightNetAdapter(BaseSiteAdapter):
self.story.setMetadata('rating', value)
elif key == 'Chapters':
print("cahpter value:%s"%value)
self.story.setMetadata('numChapters', int(value))
# Also parse reviews number which lies right after the chapters
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,22 +46,22 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
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])
# 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','osph')
# 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.
@@ -81,7 +81,7 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -95,13 +95,13 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
params['sid'] = ''
params['intent'] = ''
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['penname']))
d = self._fetchUrl(loginUrl, params)
if "Logout" not in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -129,30 +129,30 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
# 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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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)
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
try:
# in case link points somewhere other than the first chapter
a = soup.findAll('option')[1]['value']
self.story.setMetadata('storyId',a.split('=',)[1])
url = 'http://'+self.host+'/'+a
soup = bs.BeautifulSoup(self._fetchUrl(url))
soup = self.make_soup(self._fetchUrl(url))
except:
pass
@@ -196,7 +196,7 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
val = val.nextSibling
val = val.nextSibling
while not defaultGetattr(val) == 'table':
svalue += str(val)
svalue += unicode(val)
val = val.nextSibling
self.setDescription(url,svalue)
@@ -238,13 +238,13 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
# grab the text for an individual chapter.
def getChapterText(self, url):
@@ -253,11 +253,11 @@ class OcclumencySycophantHexComAdapter(BaseSiteAdapter):
data = self._fetchUrl(url)
data = data.replace('<div align="left"', '<div align="left">')
soup = bs.BeautifulSoup(data, selfClosingTags=('br','hr'))
soup = self.make_soup(data)
story = soup.find('div', {"align" : "left"})
if None == story:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,story)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,26 +46,26 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
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])
# 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','odf')
# 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 'onedirectionfanfiction.com'
@classmethod
def getAcceptDomains(cls):
return ['www.onedirectionfanfiction.com','onedirectionfanfiction.com']
@@ -77,7 +77,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return re.escape("http://")+"(www\.)?"+re.escape(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 \
@@ -86,7 +86,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -98,13 +98,13 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -115,7 +115,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
## 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
@@ -137,30 +137,30 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
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)
# 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 "Age Consent Required" in data:
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -193,8 +193,8 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while value and not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while value and 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
@@ -227,7 +227,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -240,7 +240,7 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -249,22 +249,21 @@ class OneDirectionFanfictionComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+23 -24
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2, urllib, cookielib
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -45,21 +45,21 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
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.path.split('/',)[3])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/fanfiction/story/' +self.story.getMetadata('storyId')+'/')
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','phs')
# 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.
@@ -78,7 +78,7 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -90,12 +90,12 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
params['txtpassword'] = self.getConfig("password")
#params['remember'] = '1'
params['login'] = 'Login'
loginUrl = 'http://' + self.getSiteDomain() + '/users/processlogin.php'
logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
params['txtusername']))
d = self._fetchUrl(loginUrl, params)
if 'Please login to continue.' in d : #Member Account
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['txtusername']))
@@ -126,18 +126,18 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
b = soup.find('div', {'id' : 'nav25'})
a = b.find('a', href=re.compile(r'fanfiction/story/'+self.story.getMetadata('storyId')+"/$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url. /fanfiction/stories.php?psid=125
a = b.find('a', href=re.compile(r"/fanfiction/stories.php\?psid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -160,14 +160,14 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+chapter['value']))
if i == 0:
self.story.setMetadata('storyId',chapter['value'].split('/')[3])
head = bs.BeautifulSoup(self._fetchUrl('http://'+self.host+chapter['value'])).findAll('b')
head = self.make_soup(self._fetchUrl('http://'+self.host+chapter['value'])).findAll('b')
for b in head:
if b.text == "Updated":
date = b.nextSibling.string.split(': ')[1].split(',')
self.story.setMetadata('datePublished', makeDate(date[0]+date[1], self.dateformat))
if i == (len(chapters)-1):
head = bs.BeautifulSoup(self._fetchUrl('http://'+self.host+chapter['value'])).findAll('b')
head = self.make_soup(self._fetchUrl('http://'+self.host+chapter['value'])).findAll('b')
for b in head:
if b.text == "Updated":
date = b.nextSibling.string.split(': ')[1].split(',')
@@ -177,14 +177,14 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
info = asoup.find('a', href=re.compile(r'fanfiction/story/'+self.story.getMetadata('storyId')+"/$"))
while info != None:
info = info.findNext('div')
b = info.find('b')
val = b.nextSibling
if 'Rating' in b.string:
self.story.setMetadata('rating', val.string.split(': ')[1])
@@ -200,7 +200,7 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
else:
val = 'In-Progress'
self.story.setMetadata('status', val)
if 'Summary' in b.string:
b.extract()
info.find('br').extract()
@@ -213,10 +213,9 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
chapter=bs.BeautifulSoup('<div class="story"></div>')
chapter=self.make_soup('<div class="story"></div>')
for p in soup.findAll('p'):
if "This is for problems with the formatting or the layout of the chapter." in stripHTML(p):
break
@@ -236,5 +235,5 @@ class PhoenixSongNetAdapter(BaseSiteAdapter):
if None == chapter:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,chapter)
+28 -29
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
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])
# pommedesang.com has two 'sections', shown in URL as
# 'efiction' and 'sds' that change how things should be
# handled.
# http://pommedesang.com/efiction/viewstory.php?sid=1234
# http://pommedesang.com/sds/viewstory.php?sid=1234
self.section=self.parsedUrl.path.split('/',)[1]
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/'+self.section+'/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','pmds')
@@ -70,7 +70,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
self.dateformat = "%b %d, %Y"
else:
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.
@@ -91,7 +91,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -103,13 +103,13 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/'+self.section+'/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -166,23 +166,23 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title
a = soup.find('a', href=re.compile('viewstory.php\?sid=\d+'))
self.story.setMetadata('title',stripHTML(a))
# 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])
@@ -205,7 +205,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
return d[k]
except:
return ""
# summary, rated, word count, categories, characters, genre, warnings, completed, published, updated, seires
# <span class="label">Rated:</span> NC-17<br /> etc
@@ -217,8 +217,8 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -257,7 +257,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
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))
@@ -268,7 +268,7 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+self.section+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
# can't use ^viewstory...$ in case of higher rated stories with javascript href.
storyas = seriessoup.findAll('a', href=re.compile('viewstory.php\?sid=\d+'))
i=1
@@ -280,22 +280,21 @@ class PommeDeSangComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,10 +46,10 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
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])
# normalized story URL.
if "explicit" in self.parsedUrl.netloc:
self._setURL('http://explicit.' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
@@ -57,7 +57,7 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
else:
self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
self.dateformat = "%d %b %Y"
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','pffa')
@@ -65,7 +65,7 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'ponyfictionarchive.net'
@classmethod
def getAcceptDomains(cls):
return ['www.ponyfictionarchive.net','ponyfictionarchive.net','explicit.ponyfictionarchive.net']
@@ -122,23 +122,23 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -165,27 +165,27 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
genres = soup.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
for genre in genres:
self.story.addToList('genre',genre.string)
warnings = soup.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=3'))
for warning in warnings:
self.story.addToList('warnings',warning.string)
status = soup.find('a',href=re.compile(r'browse.php\?type=class&type_id=2'))
if status: # apparently this site can have stories with neither In-Progress or Complete.
self.story.setMetadata('status',status.string)
section = soup.findAll('span', {'class' : 'General'})[1]
section = soup.findAll('span', {'class' : 'General'})[1]
self.story.setMetadata('rating', section.previousSibling.previousSibling.string)
value = section.nextSibling
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
# <span class="label">Rated:</span> NC-17<br /> etc
# <span class="label">Rated:</span> NC-17<br /> etc
labels = soup.findAll('span',{'class':'label'})
for labelspan in labels:
value = labelspan.nextSibling
@@ -207,7 +207,7 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
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))
@@ -218,7 +218,7 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -230,21 +230,21 @@ class PonyFictionArchiveNetAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url)) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+30 -30
View File
@@ -22,7 +22,7 @@ import re
import urllib2
import cookielib as cl
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -52,21 +52,21 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
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.path.split('/',)[2])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/story/'+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','prtky') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -85,7 +85,7 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
this and change it to True.
'''
return True
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -105,26 +105,26 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
comment=None,
comment_url=None,
rest={'HttpOnly': None},
rfc2109=False)
self.cookiejar.set_cookie(cookie)
rfc2109=False)
self.get_cookiejar().set_cookie(cookie)
try:
data = self._fetchUrl(url)
data = self._fetchUrl(url,usecache=False)
except urllib2.HTTPError, e:
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
if "You must be over 18 years of age to view it" in data: # XXX
if "You must be over 18 years of age to view it" in data: # XXX
raise exceptions.AdultCheckRequired(self.url)
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(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"/profile/\d+"))
#print("======a:%s"%a)
@@ -133,13 +133,13 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('author',a.string)
## Going to get the rest from the author page.
authsoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
authsoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
## Title
titlea = authsoup.find('a', href=re.compile(r'/story/'+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(titlea))
metablock = titlea.parent
# Find the chapters:
for chapter in soup.find('select',{'name':'select5'}).findAll('option', {'value':re.compile(r'/story/'+self.story.getMetadata('storyId')+"/\d+$")}):
# just in case there's tags, like <i> in chapter titles.
@@ -147,7 +147,7 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
if not chtitle:
chtitle = "(Untitled Chapter)"
self.chapterUrls.append((chtitle,'http://'+self.host+chapter['value']))
if len(self.chapterUrls) == 0:
self.chapterUrls.append((stripHTML(self.story.getMetadata('title')),url))
@@ -162,7 +162,7 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
return d[k]
except:
return ""
# <SPAN class="dark-small-bold">Contents:</SPAN> <SPAN class="small-grey">NC17 </SPAN>
# <SPAN class="dark-small-bold">Contents:</SPAN> <SPAN class="small-grey">NC17 </SPAN>
# <SPAN class="dark-small-bold">Published: </SPAN><SPAN class="small-grey">12/11/07</SPAN>
# <SPAN class="dark-small-bold"><BR>
# Description:</SPAN> <SPAN class="small-black">A special book helps Harry tap into the power the Dark Lord knows not. Of course its a book on sex magic and rituals… but Harrys not complaining. Spurned on by the ghost of a pervert founder, Harry leads his friends in the hunt for Voldemorts Horcruxes.
@@ -201,10 +201,10 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
# spans.
svalue = ""
value = labelspan.nextSibling
while not defaultGetattr(value,'class') == 'dark-small-bold':
svalue += str(value)
while 'dark-small-bold' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
for genre in svalue.split("/"):
genre = genre.strip()
if genre != 'None':
@@ -228,7 +228,7 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
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))
@@ -239,18 +239,18 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
# series_url = 'http://'+self.host+'/'+a['href']
# # use BeautifulSoup HTML parser to make everything easier to find.
# seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
# seriessoup = self.make_soup(self._fetchUrl(series_url))
# storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
# i=1
# for a in storyas:
# if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
# self.setSeries(series_name, i)
# break
# i+=1
# 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):
@@ -259,8 +259,8 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
data = data.replace("HTML>","div>")
data = data.replace("html>","div>")
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# remove ad blocks so they don't become covers.
for ns in soup.findAll('noscript'):
@@ -270,7 +270,7 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
tag = soup.find('td', {'class' : 'story'})
if tag == None and "<center><b>Chapter does not exist!</b></center>" in data:
logger.error("Chapter is missing at: %s"%url)
return self.utf8FromSoup(url,bs.BeautifulStoneSoup("<div><p><center><b>Chapter does not exist!</b></center></p><p>Chapter is missing at: <a href='%s'>%s</a></p></div>"%(url,url)))
return self.utf8FromSoup(url,self.make_soup("<div><p><center><b>Chapter does not exist!</b></center></p><p>Chapter is missing at: <a href='%s'>%s</a></p></div>"%(url,url)))
tag.name='div' # force to be a div to avoid problems with nook.
centers = tag.findAll('center')
@@ -283,5 +283,5 @@ class PortkeyOrgAdapter(BaseSiteAdapter): # XXX
if None == tag:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,tag)
@@ -23,7 +23,7 @@ import re
import urllib
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -39,15 +39,15 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
# Most sites that claim to be
# iso-8859-1 (and some that claim to be
# utf8) are really windows-1252.
# get storyId from url--url validation guarantees query is only sid=1234
self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/fanfiction/viewstory.php?sid='+self.story.getMetadata('storyId'))
@staticmethod
def getSiteDomain():
return 'www.potionsandsnitches.org'
@@ -77,15 +77,15 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
## Title
a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# 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])
@@ -99,20 +99,12 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('numChapters',len(self.chapterUrls))
## <meta name='description' content='&lt;p&gt;Description&lt;/p&gt; ...' >
## Summary, strangely, is in the content attr of a <meta name='description'> tag
## which is escaped HTML. Unfortunately, we can't use it because they don't
## escape (') chars in the desc, breakin the tag.
#meta_desc = soup.find('meta',{'name':'description'})
#metasoup = bs.BeautifulStoneSoup(meta_desc['content'])
#self.story.setMetadata('description',stripHTML(metasoup))
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:
@@ -122,8 +114,8 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next div class='listbox'
svalue = ""
while not defaultGetattr(value,'class') == 'listbox':
svalue += str(value)
while 'listbox' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -134,6 +126,9 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
if 'Word count' in label:
self.story.setMetadata('numWords', value)
if 'Read' in label:
self.story.setMetadata('reads', value)
if 'Categories' in label:
cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
catstext = [cat.string for cat in cats]
@@ -150,12 +145,30 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
else:
self.story.addToList('characters',char.string)
if 'Warning' in label:
warnings = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for warning in warnings:
self.story.addToList('warnings',stripHTML(warning))
if 'Genre' in label:
genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
genrestext = [genre.string for genre in genres]
self.genre = ', '.join(genrestext)
for genre in genrestext:
self.story.addToList('genre',genre.string)
for genre in genres:
self.story.addToList('genre',stripHTML(genre))
if 'Takes Place' in label:
takesplaces = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for takesplace in takesplaces:
self.story.addToList('takesplaces',stripHTML(takesplace))
if 'Snape flavour' in label:
snapeflavours = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for snapeflavour in snapeflavours:
self.story.addToList('snapeflavours',stripHTML(snapeflavour))
if 'Tags' in label:
sitetags = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
for sitetag in sitetags:
self.story.addToList('sitetags',stripHTML(sitetag))
if 'Completed' in label:
if 'Yes' in value:
@@ -166,7 +179,7 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
if 'Published' in label:
# limit date values, there's some extra chars.
self.story.setMetadata('datePublished', makeDate(stripHTML(value[:12]), "%d %b %Y"))
if 'Updated' in label:
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value[:12]), "%d %b %Y"))
@@ -177,7 +190,7 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/fanfiction/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -186,24 +199,31 @@ class PotionsAndSnitchesOrgSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
break
i+=1
except:
# I find it hard to care if the series parsing fails
pass
divsort = soup.find('div',id='sort')
stars = len(divsort.find_all('img',src='images/star.gif'))
stars = stars + 0.5 * len(divsort.find_all('img',src='images/starhalf.gif'))
self.story.setMetadata('stars',stars)
a = divsort.find_all('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
def getClass():
+24 -25
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -40,7 +40,7 @@ class PotterFicsComAdapter(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
@@ -49,12 +49,12 @@ class PotterFicsComAdapter(BaseSiteAdapter):
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 correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL. gets rid of chapter if there, left with chapter index URL
nurl = "http://"+self.getSiteDomain()+"/historias/"+self.story.getMetadata('storyId')
self._setURL(nurl)
@@ -62,8 +62,8 @@ class PotterFicsComAdapter(BaseSiteAdapter):
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','potficscom')
@@ -94,7 +94,7 @@ class PotterFicsComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self,url):
params = {}
@@ -157,7 +157,7 @@ class PotterFicsComAdapter(BaseSiteAdapter):
if "Esta historia no existe. Probablemente ha sido eliminada." in data:
raise exceptions.StoryDoesNotExist(self.url)
##print data
#deal with adult content login
@@ -165,7 +165,7 @@ class PotterFicsComAdapter(BaseSiteAdapter):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url,usecache=False)
#set constant meta for this site:
#Set Language = Spanish
self.story.setMetadata('language', 'Spanish')
@@ -174,12 +174,12 @@ class PotterFicsComAdapter(BaseSiteAdapter):
# by adding a section for this site with the line:
# extracategories:Harry Potter
#self.story.addToList('category','Harry Potter')
#get the rest of the meta
# use BeautifulSoup HTML parser to make everything easier to find.
#self closing br and img present!
soup = bs.BeautifulSoup(data,selfClosingTags=('br','img'))
soup = self.make_soup(data)
#we want the second table directly under the body, contains all the metadata
table = soup.html.body.findAll('table', recursive=False)[1]
#within that, we want the second row, first cell
@@ -223,11 +223,11 @@ class PotterFicsComAdapter(BaseSiteAdapter):
hour = int(time[0])
minute = int(time[1])
self.story.setMetadata('dateUpdated', datetime.datetime(year, month, day, hour, minute))
mb = mb.span.findNextSibling('span').findNextSibling('span')
wc = mb.find(text=re.compile(' palabras en total')).strip()
self.story.setMetadata('numWords', wc.split()[0])
#then we come to categories and genres. Oh dear. On this site, categories hold everything from genre, to ships, to crossovers.
#To make things worse, there is also another genre field, which often holds similar/duplicate info. Links to genre pages do not work
#though, so perhaps those will be phased out?
@@ -235,7 +235,7 @@ class PotterFicsComAdapter(BaseSiteAdapter):
links = mb.findAll('a',href=re.compile('/(categorias|generos)/\d+'))
genlist = [i.string.strip() for i in links]
self.story.extendList('genre',genlist)
#get the chapter urls
#we can go back to the table cell we found before
#get its last element and work backwards to find the last ordered list on the page
@@ -245,40 +245,39 @@ class PotterFicsComAdapter(BaseSiteAdapter):
chnum = 0
for li in list:
chnum += 1
chTitle = str(chnum) + '. ' + li.a.b.string.strip()
chTitle = unicode(chnum) + '. ' + li.a.b.string.strip()
chURL = makeAbsoluteURL(li.a['href'])
chapters.append((chTitle,chURL))
#Get reviews, add to total
revs += int(li.div.a.string.split()[0])
self.chapterUrls.extend(chapters)
self.story.setMetadata('numChapters', len(chapters))
self.story.setMetadata('reviews', revs)
#Now for the description... this may be tricky...
#if it is there (doesn't have to be), it will be before the chapter list,
#separated by a horizontal rule, and after the google ad bar
#get list's parent div
mb = list.parent
#get the div before that, will either be the description, or the google ad bar
mb = mb.findPreviousSibling('div')
if 'google_ad_client' in str(mb):
if 'google_ad_client' in unicode(mb):
#couldn't find description, leaving it blank
pass
else:
self.setDescription(url,mb)
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr','img'))
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'cuerpoHistoria'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
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])
# 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','pha')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %b %Y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -81,7 +81,7 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +93,13 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -137,12 +137,12 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url)
# Since the warning text can change by warning level, let's
# look for the warning pass url. ksarchive uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -165,25 +165,25 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
pagetitle = soup.find('div',{'id':'pagetitle'})
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = pagetitle.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -216,8 +216,8 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -256,7 +256,7 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -269,7 +269,7 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -278,22 +278,21 @@ class PotterHeadsAnonymousComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,26 +46,26 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/missingpieces/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','ptdc')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
return 'pretendercentre.com'
@classmethod
def getAcceptDomains(cls):
return ['www.pretendercentre.com','pretendercentre.com']
@@ -120,23 +120,23 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -159,7 +159,7 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
labels = soup.findAll('span',{'class':'label'})
@@ -170,8 +170,8 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -210,7 +210,7 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
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))
@@ -221,7 +221,7 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/missingpieces/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -233,22 +233,21 @@ class PretenderCenterComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
div = soup.find('div', {'id' : 'story1'})
if None == div:
raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
return self.utf8FromSoup(url,div)
+23 -24
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -33,7 +33,7 @@ def getClass():
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class PsychFicComAdapter(BaseSiteAdapter):
class PsychFicComAdapter(BaseSiteAdapter):
def __init__(self, config, url):
BaseSiteAdapter.__init__(self, config, url)
@@ -46,21 +46,21 @@ class PsychFicComAdapter(BaseSiteAdapter):
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])
# 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','psyf')
# 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.
@@ -116,23 +116,23 @@ class PsychFicComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# 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])
@@ -165,8 +165,8 @@ class PsychFicComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -205,7 +205,7 @@ class PsychFicComAdapter(BaseSiteAdapter):
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))
@@ -216,7 +216,7 @@ class PsychFicComAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -228,22 +228,21 @@ class PsychFicComAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+28 -29
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class QafFicComAdapter(BaseSiteAdapter):
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])
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/atp/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','atp')
# 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.
@@ -72,7 +72,7 @@ class QafFicComAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain()+"/atp/viewstory.php?sid=")+r"\d+$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -117,43 +117,43 @@ class QafFicComAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title and author
a = soup.find('div', {'id' : 'pagetitle'})
aut = a.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',aut['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/atp/'+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
self.story.setMetadata('title',stripHTML(a)[:(len(a.string)-3)])
# Find the chapters:
chapters=soup.find('select')
if chapters != None:
for chapter in chapters.findAll('option'):
# just in case there's tags, like <i> in chapter titles.
# just in case there's tags, like <i> in chapter titles.
self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/atp/viewstory.php?sid='+self.story.getMetadata('storyId')+'&chapter='+chapter['value']))
else:
self.chapterUrls.append((self.story.getMetadata('title'),url))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox\s+')}):
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox')}):
a = list.find('a')
if ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
break
@@ -177,9 +177,9 @@ class QafFicComAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'classification' and value != None:
while value and 'classification' not in defaultGetattr(value,'class'):
if "Featured Stories" not in value:
svalue += str(value)
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -218,7 +218,7 @@ class QafFicComAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value.split(' ::')[0]), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -230,7 +230,7 @@ class QafFicComAdapter(BaseSiteAdapter):
# Find Series name from series URL.
series_url = 'http://'+self.host+'/atp/'+series['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -244,22 +244,21 @@ class QafFicComAdapter(BaseSiteAdapter):
i+=1
if i == 0:
break
except:
# I find it hard to care if the series parsing fails
pass
# grab the text for an individual chapter.
def getChapterText(self, url):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+53 -48
View File
@@ -5,6 +5,7 @@ import datetime
from .. import exceptions
from base_adapter import BaseSiteAdapter
from ..htmlcleanup import stripHTML
SITE_DOMAIN = 'quotev.com'
STORY_URL_TEMPLATE = 'http://www.quotev.com/story/%s'
@@ -53,76 +54,80 @@ class QuotevComAdapter(BaseSiteAdapter):
soup = self.make_soup(data)
element = soup.find('div', {'class': 'result_head'})
element = soup.find('div', {'class': 'result'})
if not element:
raise exceptions.StoryDoesNotExist(self.url)
self.story.setMetadata('title', element.find('span', recursive=False).get_text())
self.story.setMetadata('title', element.find('h1').get_text())
element = soup.find('div', {'class': 'desc_creator'})
if element:
a = element('a')[1]
self.story.setMetadata('author', a.get_text())
self.story.setMetadata('authorId', get_url_path_segments(a['href'])[0])
self.story.setMetadata('authorUrl', urlparse.urljoin(self.url, a['href']))
# Multiple authors
else:
element = soup.find('div', id='qheadx')
for a in element('div', recursive=False)[1]('a'):
author = a.get_text()
if not a.get_text():
continue
self.story.addToList('author', author)
self.story.addToList('authorId', get_url_path_segments(a['href'])[0])
# quotev html is all about formatting without any content tagging
authdiv = soup.find('div', {'style':"text-align:left;"})
if authdiv:
#print("div:%s"%authdiv.find_all('a'))
for a in authdiv.find_all('a'):
self.story.addToList('author', a.get_text())
self.story.addToList('authorId', a['href'].split('/')[-1])
self.story.addToList('authorUrl', urlparse.urljoin(self.url, a['href']))
else:
self.story.setMetadata('author','Anonymous')
self.story.setMetadata('authorUrl','http://www.quotev.com')
self.story.setMetadata('authorId','0')
self.setDescription(self.url, soup.find('div', id='qdesct'))
self.setCoverImage(self.url, urlparse.urljoin(self.url, soup.find('img', {'class': 'logo'})['src']))
for a in soup.find('div', {'class': 'tag'})('a'):
if a['href'] == '#':
continue
imgmeta = soup.find('meta',{'property':"og:image" })
if imgmeta:
self.setCoverImage(self.url, urlparse.urljoin(self.url, imgmeta['content']))
for a in soup.find_all('a', {'href': re.compile(SITE_DOMAIN+'/stories/c/')}):
self.story.addToList('category', a.get_text())
elements = soup('span', {'class': 'q_time'})
for a in soup.find_all('a', {'href': re.compile(SITE_DOMAIN+'/search/')}):
self.story.addToList('searchtags', a.get_text())
elements = soup.find_all('span', {'class': 'q_time'})
self.story.setMetadata('datePublished', datetime.datetime.fromtimestamp(float(elements[0]['ts'])))
if len(elements) > 1:
self.story.setMetadata('dateUpdated', datetime.datetime.fromtimestamp(float(elements[1]['ts'])))
metadiv = elements[0].parent
if 'completed' in stripHTML(metadiv):
self.story.setMetadata('status', 'Completed')
else:
self.story.setMetadata('status', 'In-Progress')
data = filter(None, (x.strip() for x in stripHTML(metadiv).split(u'\xb7')))
for datum in data:
parts = datum.split()
if len(parts) < 2 or parts[1] not in self.getConfig('extra_valid_entries'):
continue
# Not a valid metadatum
# if not len(parts) == 2:
# continue
key, value = parts[1], parts[0]
self.story.setMetadata(key, value.replace(',', '').replace('.', ''))
favspans = soup.find('a',{'id':'fav_btn'}).find_all('span')
if len(favspans) > 1:
self.story.setMetadata('favorites', stripHTML(favspans[1]).replace(',', ''))
commentspans = soup.find('a',{'id':'comment_btn'}).find_all('span')
#print("commentspans:%s"%commentspans)
if len(commentspans) > 0:
self.story.setMetadata('comments', stripHTML(commentspans[0]).replace(',', ''))
for a in soup.find('div', id='rselect')('a'):
self.chapterUrls.append((a.get_text(), urlparse.urljoin(self.url, a['href'])))
self.story.setMetadata('numChapters', len(self.chapterUrls))
element = soup.find('div', {'class': 't'})('div', recursive=False)[1].div
data = filter(None, (x.strip() for x in element.get_text().split(u'\xb7')))
if 'completed' in data:
self.story.setMetadata('status', 'Completed')
data.remove('completed')
else:
self.story.setMetadata('status', 'In-Progress')
for datum in data:
parts = datum.split()
# Not a valid metadatum
if not len(parts) == 2:
continue
key, value = parts
self.story.setMetadata(key, value.replace(',', '').replace('.', ''))
self.story.setMetadata('favorites', soup.find('div', id='favqn').get_text())
element = soup.find('a', id='comment_btn').span
self.story.setMetadata('comments', element.get_text() if element else 0)
def getChapterText(self, url):
data = self._fetchUrl(url)
soup = self.make_soup(data)
element = soup.find('div', id='restxt')
element = soup.find('div', id='rescontent')
for a in element('a'):
a.unwrap()
@@ -23,7 +23,7 @@ import urllib2
import cookielib as cl
from datetime import datetime
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -33,37 +33,37 @@ def getClass():
return RestrictedSectionOrgSiteAdapter
class RestrictedSectionOrgSiteAdapter(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 = ""
# normalized story URL.
# get story/file and storyId from url--url validation guarantees query correct
m = re.match(self.getSiteURLPattern(),url)
if m:
self.story.setMetadata('storyId',m.group('id'))
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/' + m.group('filestory') + '.php?' + m.group('filestory') + '=' + self.story.getMetadata('storyId'))
else:
raise exceptions.InvalidStoryURL(url,
self.getSiteDomain(),
self.getSiteExampleURLs())
self.story.setMetadata('siteabbrev','ressec')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d %b %Y" # 20 Nov 2005
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
return 'www.restrictedsection.org'
@@ -74,7 +74,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
def getSiteURLPattern(self):
return re.escape("http://"+self.getSiteDomain())+r"/(?P<filestory>file|story).php\?(file|story)=(?P<id>\d+)$"
## Getting the chapter list and the meta data, plus 'is adult' checking.
def extractChapterUrlsAndMetadata(self):
@@ -88,7 +88,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
try:
if 'file' in url:
data = self._postUrlUP(url)
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
storya = soup.find('a',href=re.compile(r"^story.php\?story=\d+"))
url = 'http://'+self.host+'/'+storya['href'].split('&')[0] # strip rs_session
@@ -97,7 +97,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
for filea in fileas:
if 'Previous Chapter' in filea.string or 'Next Chapter' in filea.string:
raise exceptions.FailedToDownload(self.getSiteDomain() +" Cannot use chapter url with multi-chapter stories on this site.")
logger.debug("metadata URL: "+url)
data = self._fetchUrl(url)
# print data
@@ -108,9 +108,9 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
raise e
if "Story not found" in data:
raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Story not found.")
# use BeautifulSoup HTML parser to make everything easier to find.
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
# check user/pass on a chapter for multi-chapter
if 'file' not in self.url:
@@ -122,7 +122,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
# Find authorid and URL from... author url.
a = h2.find('a')
ahref = a['href'].split('&')[0] # strip rs_session
self.story.setMetadata('authorId',ahref.split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+ahref)
self.story.setMetadata('author',stripHTML(a))
@@ -145,24 +145,24 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('numWords',"%s"%wordcount)
self.story.setMetadata('rating', soup.find('a',href=re.compile(r"^rating.php\?rating=\d+")).string)
# other tags
labels = soup.find('table', {'class':'info'}).findAll('th')
for labelspan in labels:
value = labelspan.nextSibling
label = labelspan.string
if label != None:
if 'Categories' in label:
for g in stripHTML(value).split('\n'):
self.story.addToList('genre',g)
if 'Pairings' in label:
for g in stripHTML(value).split('\n'):
self.story.addToList('ships',g)
if 'Summary' in label:
self.setDescription(url,stripHTML(value).replace("\n"," ").replace("\r",""))
value.extract() # remove summary incase it contains file URLs.
@@ -176,7 +176,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
if 'Length' in label: # one-shots only.
self.story.setMetadata('numWords',value.string[:-6])
# one-shot.
if 'file' in self.url:
self.chapterUrls.append((self.story.getMetadata('title'),self.url))
@@ -226,7 +226,7 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
logger.error("Giving up on %s" %url)
logger.exception(excpt)
raise(excpt)
# grab the text for an individual chapter.
def getChapterText(self, url):
@@ -234,14 +234,14 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
data = self._postUrlUP(url)
#print("data:%s"%data)
# some stories have html that confuses the parser. For story
# text we don't care about anything before '<table id="page"'
# and seems to clear the issue.
data = data[data.index('<table id="page"'):]
soup = bs.BeautifulSoup(data)
soup = self.make_soup(data)
div = soup.find('td',{'id':'page_content'})
div.name='div'
@@ -255,11 +255,11 @@ class RestrictedSectionOrgSiteAdapter(BaseSiteAdapter):
for tag in hr.findAllPrevious():
tag.extract()
hr.extract()
# Remove all tags after the last <hr> (including hr)
hr = div.findAll('hr')[-1]
for tag in hr.findAllNext():
tag.extract()
hr.extract()
return self.utf8FromSoup(url,div)
+35 -36
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -69,22 +69,22 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/fanfics/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','sjn') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -105,7 +105,7 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -117,13 +117,13 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/fanfics/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -165,12 +165,12 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
# 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.
# Since the warning text can change by warning level, let's
# look for the warning pass url. nfacommunity uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -193,15 +193,15 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
@@ -210,7 +210,7 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
# (fetch multiple authors)
alist = soup.findAll('a', href=re.compile(r"viewuser.php\?uid=\d+"))
@@ -218,12 +218,12 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
self.story.addToList('authorId',a['href'].split('=')[1])
self.story.addToList('authorUrl','http://'+self.host+'/fanfics/'+a['href'])
self.story.addToList('author',a.string)
# Reviews
reviewdata = soup.find('div', {'id' : 'sort'})
a = reviewdata.findAll('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
self.story.setMetadata('reviews',stripHTML(a))
# 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.
@@ -240,16 +240,16 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
return d[k]
except:
return ""
# <span class="label">Rated:</span> NC-17<br /> etc
# <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
label = labelspan.string
if 'Summary' in label:
self.setDescription(url,value)
self.setDescription(url,value)
if 'Rated' in label:
self.story.setMetadata('rating', value)
@@ -298,12 +298,12 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
value=value.replace(' | ','')
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
try:
# Find Series name from series URL.
@@ -312,7 +312,7 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/fanfics/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -321,22 +321,21 @@ class SamAndJackNetAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+26 -27
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
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])
# 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','sda')
# 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.
@@ -92,25 +92,25 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
## Title and author
a = soup.find('div', {'id' : 'pagetitle'})
aut = a.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',aut['href'].split('=')[1])
self.story.setMetadata('authorUrl','http://'+self.host+'/'+aut['href'])
self.story.setMetadata('author',aut.string)
aut.extract()
self.story.setMetadata('title',stripHTML(a)[:(len(stripHTML(a))-3)])
# Find the chapters:
@@ -123,10 +123,10 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
self.chapterUrls.append((self.story.getMetadata('title'),url))
self.story.setMetadata('numChapters',len(self.chapterUrls))
asoup = bs.BeautifulSoup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox\s+')}):
asoup = self.make_soup(self._fetchUrl(self.story.getMetadata('authorUrl')))
for list in asoup.findAll('div', {'class' : re.compile('listbox')}):
a = list.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
if a != None:
break
@@ -150,8 +150,8 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'classification':
svalue += str(value)
while 'classification' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -190,7 +190,7 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
if 'Published' in label:
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -203,7 +203,7 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -212,22 +212,21 @@ class SamDeanArchiveNuAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
+34 -35
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
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])
# 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','shn')
# The date format will vary from site to site.
# http://docs.python.org/library/datetime.html#strftime-strptime-behavior
self.dateformat = "%d/%m/%y"
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -81,7 +81,7 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -93,13 +93,13 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -137,12 +137,12 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
# need to log in for this one.
self.performLogin(url)
data = self._fetchUrl(url)
# Since the warning text can change by warning level, let's
# look for the warning pass url. ksarchive uses
# &amp;warning= -- actually, so do other sites. Must be an
# eFiction book.
# viewstory.php?sid=1882&amp;warning=4
# viewstory.php?sid=1654&amp;ageconsent=ok&amp;warning=5
#print data
@@ -165,25 +165,25 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
pagetitle = soup.find('tr',{'valign':'top'})
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
a = pagetitle.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -199,7 +199,7 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
# eFiction sites don't help us out a lot with their meta data
# formating, so it's a little ugly.
cats = soup.findAll('a',href=re.compile(r'browse.php\?type=categories'))
for cat in cats:
if '/' == cat.string[0]:
@@ -210,9 +210,9 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
self.story.addToList('category',cat.string)
if '(' in cat.string:
self.story.addToList('category',cat.string.split('(')[1].split(')')[0])
chars = soup.findAll('a',href=re.compile(r'browse.php\?type=characters'))
for char in chars:
@@ -225,7 +225,7 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
warnings = soup.findAll('a',href=re.compile(r'browse.php\?type=class&type_id=1'))
for warning in warnings:
self.story.addToList('warnings',warning.string)
textsoup = stripHTML(soup)
a = textsoup.split('Published: ')[1].split(' ')[0]
@@ -243,18 +243,18 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
self.story.setMetadata('status', 'In-Progress')
#a = textsoup.split('Summary: ')[1].split('Add Story to Favorites')[0]
#self.setDescription(url,a)
a=soup.find(text=re.compile("Summary: "))
i=0
svalue = ""
while i == 0:
try:
b = str(a)
b = unicode(a)
svalue += b.split('Summary: ')[1]
except:
svalue += str(a)
svalue += unicode(a)
if a.nextSibling != None:
a = a.nextSibling
else:
@@ -270,7 +270,7 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -279,22 +279,21 @@ class ScarHeadNetAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -46,21 +46,21 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
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])
# 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.
@@ -125,23 +125,23 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
if e.code == 404:
raise exceptions.StoryDoesNotExist(self.url)
else:
raise e
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(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',stripHTML(a))
# Find authorid and URL from... author url.
a = soup.find('div',{"id":"pagetitle"}).find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
self.story.setMetadata('authorId',a['href'].split('=')[1])
@@ -174,8 +174,8 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
if 'Summary' in label:
## Everything until the next span class='label'
svalue = ""
while not defaultGetattr(value,'class') == 'label':
svalue += str(value)
while 'label' not in defaultGetattr(value,'class'):
svalue += unicode(value)
value = value.nextSibling
self.setDescription(url,svalue)
#self.story.setMetadata('description',stripHTML(svalue))
@@ -204,7 +204,7 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
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))
@@ -215,7 +215,7 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
series_url = 'http://'+self.host+'/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(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
@@ -227,22 +227,21 @@ class ScarvesAndCoffeeNetAdapter(BaseSiteAdapter):
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)
@@ -1,259 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2012 Fanficdownloader team, 2015 FanFicFare team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Software: eFiction
import time
import logging
logger = logging.getLogger(__name__)
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 SG1HeliopolisComAdapter
# Class name has to be unique. Our convention is camel case the
# sitename with Adapter at the end. www is skipped.
class SG1HeliopolisComAdapter(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])
self.section=self.parsedUrl.path.split('/',)[1]
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/'+self.section+'/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','sghp')
# 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. Can't use extracategories, could be Atlantis or SG-1
if 'atlantis' in self.section:
self.story.addToList("category","Stargate: Atlantis")
else:
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 = "%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 'sg1-heliopolis.com'
@classmethod
def getSiteExampleURLs(cls):
return "http://"+cls.getSiteDomain()+"/archive/viewstory.php?sid=1234 http://"+cls.getSiteDomain()+"/adult/viewstory.php?sid=1234 http://"+cls.getSiteDomain()+"/atlantis/viewstory.php?sid=1234"
def getSiteURLPattern(self):
return r"http://sg1-heliopolis.com/(archive|adult|atlantis)?/viewstory.php\?sid=\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=4"
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
logger.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+((?:&amp;ageconsent=ok)?&amp;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 &amp; error in url.
addurl = addurl.replace("&amp;","&")
url = self.url+'&index=1'+addurl
logger.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',stripHTML(a))
# 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+'/'+self.section+'/'+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)
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+'/'+self.section+'/'+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)
self.story.setMetadata('seriesUrl',series_url)
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):
logger.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)
+32 -33
View File
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
import re
import urllib2
from .. import BeautifulSoup as bs
from ..htmlcleanup import stripHTML
from .. import exceptions as exceptions
@@ -69,22 +69,22 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
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])
# normalized story URL.
# XXX Most sites don't have the /fanfic part. Replace all to remove it usually.
self._setURL('http://' + self.getSiteDomain() + '/fanfics/viewstory.php?sid='+self.story.getMetadata('storyId'))
# Each adapter needs to have a unique site abbreviation.
self.story.setMetadata('siteabbrev','swf') # 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
@staticmethod # must be @staticmethod, don't remove it.
def getSiteDomain():
# The site domain. Does have www here, if it uses it.
@@ -105,7 +105,7 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
return True
else:
return False
def performLogin(self, url):
params = {}
@@ -117,13 +117,13 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
params['password'] = self.getConfig("password")
params['cookiecheck'] = '1'
params['submit'] = 'Submit'
loginUrl = 'http://' + self.getSiteDomain() + '/fanfics/user.php?action=login'
logger.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
logger.info("Failed to login to URL %s as %s" % (loginUrl,
params['penname']))
@@ -165,14 +165,14 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
# 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 "Age Consent Required" in data: # XXX
if "Age Consent Required" in data: # XXX
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.")
raise exceptions.AccessDenied(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)
soup = self.make_soup(data)
# print data
# Now go hunting for all the meta data and the chapter list.
@@ -181,7 +181,7 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
## Title
a = pagetitle.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
self.story.setMetadata('title',stripHTML(a))
# Find authorid and URL from... author url.
# (fetch multiple authors)
alist = soup.findAll('a', href=re.compile(r"viewuser.php\?uid=\d+"))
@@ -189,12 +189,12 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
self.story.addToList('authorId',a['href'].split('=')[1])
self.story.addToList('authorUrl','http://'+self.host+'/fanfics/'+a['href'])
self.story.addToList('author',a.string)
# Reviews
reviewdata = soup.find('div', {'id' : 'sort'})
a = reviewdata.findAll('a', href=re.compile(r'reviews.php\?type=ST&(amp;)?item='+self.story.getMetadata('storyId')+"$"))[1] # second one.
self.story.setMetadata('reviews',stripHTML(a))
self.story.setMetadata('reviews',stripHTML(a))
# Find the chapters:
@@ -213,19 +213,19 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
return d[k]
except:
return ""
# Summary
# Summary
summarydata = unicode(soup.find('div',{'class':'content'}))
start='<span class="label">Summary: </span>'
end='</div>'
summarydata = summarydata[summarydata.index(start)+len(start):summarydata.rindex(end)]
self.setDescription(url,bs.BeautifulSoup(summarydata))
# <span class="label">Rated:</span> NC-17<br /> etc
self.setDescription(url,self.make_soup(summarydata))
# <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
label = labelspan.string
if 'Rated' in label:
self.story.setMetadata('rating', value)
@@ -274,7 +274,7 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
if 'Published' in label:
value=value.replace(' - ','')
self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
if 'Updated' in label:
# there's a stray [ at the end.
#value = value[0:-1]
@@ -287,7 +287,7 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
series_url = 'http://'+self.host+'/fanfics/'+a['href']
# use BeautifulSoup HTML parser to make everything easier to find.
seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
seriessoup = self.make_soup(self._fetchUrl(series_url))
storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
i=1
for a in storyas:
@@ -296,22 +296,21 @@ class SheppardWeirComAdapter(BaseSiteAdapter): # XXX
self.story.setMetadata('seriesUrl',series_url)
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):
logger.debug('Getting chapter text from: %s' % url)
soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
soup = self.make_soup(self._fetchUrl(url))
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)

Some files were not shown because too many files have changed in this diff Show More