diff --git a/fanficdownloader/adapters/adapter_fanfictionnet.py b/fanficdownloader/adapters/adapter_fanfictionnet.py
index aefd5f03..b6a713df 100644
--- a/fanficdownloader/adapters/adapter_fanfictionnet.py
+++ b/fanficdownloader/adapters/adapter_fanfictionnet.py
@@ -84,6 +84,9 @@ class FanFictionNetSiteAdapter(BaseSiteAdapter):
if "Unable to locate story with id of " in data:
raise exceptions.StoryDoesNotExist(url)
+ if "Chapter not found. Please check to see you are not using an outdated url." in data:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! 'Chapter not found. Please check to see you are not using an outdated url.'" % url)
+
# 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])
diff --git a/fanficdownloader/adapters/adapter_fictionpresscom.py b/fanficdownloader/adapters/adapter_fictionpresscom.py
index 51be29e0..76b2353a 100644
--- a/fanficdownloader/adapters/adapter_fictionpresscom.py
+++ b/fanficdownloader/adapters/adapter_fictionpresscom.py
@@ -21,11 +21,6 @@ import re
import urllib2
import time
-import fanficdownloader.BeautifulSoup as bs
-import fanficdownloader.exceptions as exceptions
-
-from base_adapter import BaseSiteAdapter, utf8FromSoup, makeDate
-
## They're from the same people and pretty much identical.
from adapter_fanfictionnet import FanFictionNetSiteAdapter
diff --git a/fanficdownloader/adapters/adapter_tenhawkpresentscom.py b/fanficdownloader/adapters/adapter_tenhawkpresentscom.py
new file mode 100644
index 00000000..9251798d
--- /dev/null
+++ b/fanficdownloader/adapters/adapter_tenhawkpresentscom.py
@@ -0,0 +1,219 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2011 Fanficdownloader team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import time
+import logging
+import re
+import urllib
+import urllib2
+
+import fanficdownloader.BeautifulSoup as bs
+from fanficdownloader.htmlcleanup import stripHTML
+import fanficdownloader.exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, utf8FromSoup, makeDate
+
+class TenhawkPresentsComSiteAdapter(BaseSiteAdapter):
+
+ def __init__(self, config, url):
+ BaseSiteAdapter.__init__(self, config, url)
+ self.story.setMetadata('siteabbrev','thpc')
+ self.decode = ["Windows-1252",
+ "utf8"] # 1252 is a superset of iso-8859-1.
+ # Most sites that claim to be
+ # iso-8859-1 (and some that claim to be
+ # utf8) are really windows-1252.
+ self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
+ self.password = ""
+ self.is_adult=False
+
+ # get storyId from url--url validation guarantees query is only sid=1234
+ self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
+ logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
+
+ # normalized story URL.
+ self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
+ self.dateformat = "%b %d %Y"
+
+
+ @staticmethod
+ def getSiteDomain():
+ return 'fanfiction.tenhawkpresents.com'
+
+ def getSiteExampleURLs(self):
+ return "http://"+self.getSiteDomain()+"/viewstory.php?sid=1234"
+
+ def getSiteURLPattern(self):
+ return re.escape("http://"+self.getSiteDomain()+"/viewstory.php?sid=")+r"\d+$"
+
+ def needToLoginCheck(self, data):
+ if 'Registered Users Only' in data \
+ or 'There is no such account on our website' in data \
+ or "That password doesn't match the one in our database" in data:
+ return True
+ else:
+ return False
+
+ def performLogin(self, url):
+ params = {}
+
+ if self.password:
+ params['penname'] = self.username
+ params['password'] = self.password
+ else:
+ params['penname'] = self.getConfig("username")
+ params['password'] = self.getConfig("password")
+ params['cookiecheck'] = '1'
+ params['submit'] = 'Submit'
+
+ loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
+ logging.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
+ params['penname']))
+
+ d = self._fetchUrl(loginUrl, params)
+
+ if "Member Account" not in d : #Member Account
+ logging.info("Failed to login to URL %s as %s" % (loginUrl,
+ params['penname']))
+ raise exceptions.FailedToLogin(url,params['penname'])
+ return False
+ else:
+ return True
+
+ def extractChapterUrlsAndMetadata(self):
+
+ if self.is_adult or self.getConfig("is_adult"):
+ addurl = "&ageconsent=ok&warning=3"
+ else:
+ addurl=""
+
+ url = self.url+'&index=1'+addurl
+ logging.debug("URL: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+
+ if self.needToLoginCheck(data):
+ # need to log in for this one.
+ addurl = "&ageconsent=ok&warning=4"
+ url = self.url+'&index=1'+addurl
+ logging.debug("Changing URL: "+url)
+ self.performLogin(url)
+ data = self._fetchUrl(url)
+
+ 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.")
+
+ # use BeautifulSoup HTML parser to make everything easier to find.
+ soup = bs.BeautifulSoup(data)
+
+ ## Title
+ a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')))
+ self.story.setMetadata('title',a.string)
+
+ # Find authorid and URL from... author url.
+ a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
+ self.story.setMetadata('authorId',a['href'].split('=')[1])
+ self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
+ self.story.setMetadata('author',a.string)
+
+ # Find the chapters:
+ for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
+ # just in case there's tags, like in chapter titles.
+ self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['href']+addurl))
+
+ self.story.setMetadata('numChapters',len(self.chapterUrls))
+
+ def defaultGetattr(d,k):
+ try:
+ return d[k]
+ except:
+ return ""
+
+ # Rated: NC-17
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.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'))
+ catstext = [cat.string for cat in cats]
+ for cat in catstext:
+ self.story.addToList('category',cat.string)
+
+ 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)
+
+ 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:
+ # there's a stray [ at the end.
+ #value = value[0:-1]
+ self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
+
+
+ def getChapterText(self, url):
+
+ logging.debug('Getting chapter text from: %s' % url)
+
+ soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
+ selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
+
+ span = soup.find('div', {'id' : 'story'})
+
+ if None == span:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+
+ return utf8FromSoup(span)
+
+def getClass():
+ return TenhawkPresentsComSiteAdapter
+
diff --git a/fanficdownloader/adapters/adapter_thewriterscoffeeshopcom.py b/fanficdownloader/adapters/adapter_thewriterscoffeeshopcom.py
index 1abe02c0..5a399a6c 100644
--- a/fanficdownloader/adapters/adapter_thewriterscoffeeshopcom.py
+++ b/fanficdownloader/adapters/adapter_thewriterscoffeeshopcom.py
@@ -47,6 +47,7 @@ class TheWritersCoffeeShopComSiteAdapter(BaseSiteAdapter):
# normalized story URL.
self._setURL('http://' + self.getSiteDomain() + '/library/viewstory.php?sid='+self.story.getMetadata('storyId'))
+ self.dateformat = "%B %d, %Y"
@staticmethod
@@ -188,12 +189,12 @@ class TheWritersCoffeeShopComSiteAdapter(BaseSiteAdapter):
self.story.setMetadata('status', 'In-Progress')
if 'Published' in label:
- self.story.setMetadata('datePublished', makeDate(stripHTML(value), "%B %d, %Y"))
+ 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), "%B %d, %Y"))
+ self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
def getChapterText(self, url):
diff --git a/index.html b/index.html
index 423a34be..e3819abd 100644
--- a/index.html
+++ b/index.html
@@ -54,6 +54,10 @@
much easier.
+ New Site: + Now supporting fanfiction.tenhawkpresents.com. +
If you have any problems with this application, please
report them in
@@ -175,6 +179,11 @@
Use the URL of the story's chapter list, such as
http://www.thewriterscoffeeshop.com/library/viewstory.php?sid=2110.
+