diff --git a/fanficdownloader/adapters/adapter_darksolaceorg.py b/fanficdownloader/adapters/adapter_darksolaceorg.py
new file mode 100644
index 0000000..fc90c74
--- /dev/null
+++ b/fanficdownloader/adapters/adapter_darksolaceorg.py
@@ -0,0 +1,279 @@
+# -*- 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 urllib2
+
+from .. import BeautifulSoup as bs
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, makeDate
+
+def getClass():
+ return DarkSolaceOrgAdapter
+
+# Class name has to be unique. Our convention is camel case the
+# sitename with Adapter at the end. www is skipped.
+class DarkSolaceOrgAdapter(BaseSiteAdapter):
+
+ def __init__(self, config, url):
+ BaseSiteAdapter.__init__(self, config, url)
+
+ self.decode = ["Windows-1252",
+ "utf8"] # 1252 is a superset of iso-8859-1.
+ # Most sites that claim to be
+ # iso-8859-1 (and some that claim to be
+ # utf8) are really windows-1252.
+ self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
+ self.password = ""
+ self.is_adult=False
+
+ # get storyId from url--url validation guarantees query is only sid=1234
+ self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
+ logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
+
+
+ # normalized story URL.
+ self._setURL('http://' + self.getSiteDomain() + '/elysian/viewstory.php?sid='+self.story.getMetadata('storyId'))
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','dksl')
+
+ # If all stories from the site fall into the same category,
+ # the site itself isn't likely to label them as such, so we
+ # do.
+ self.story.addToList("category","Buffy the Vampire Slayer")
+
+ # The date format will vary from site to site.
+ # http://docs.python.org/library/datetime.html#strftime-strptime-behavior
+ self.dateformat = "%d %B %Y"
+
+ @staticmethod # must be @staticmethod, don't remove it.
+ def getSiteDomain():
+ # The site domain. Does have www here, if it uses it.
+ return 'dark-solace.org'
+
+ @classmethod
+ def getAcceptDomains(cls):
+ return ['www.dark-solace.org','dark-solace.org']
+
+ def getSiteExampleURLs(self):
+ return "http://"+self.getSiteDomain()+"/elysian/viewstory.php?sid=1234"
+
+ def getSiteURLPattern(self):
+ return re.escape("http://")+"(www\.)?"+re.escape(self.getSiteDomain()+"/elysian/viewstory.php?sid=")+r"\d+$"
+
+ ## Login seems to be reasonably standard across eFiction sites.
+ def needToLoginCheck(self, data):
+ if 'This story contains adult content not suitable for children' 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['rememberme'] = '1'
+ params['sid'] = ''
+ params['intent'] = ''
+ params['submit'] = 'Submit'
+
+ loginUrl = 'http://' + self.getSiteDomain() + '/elysian/user.php'
+ logging.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
+ params['penname']))
+
+ d = self._fetchUrl(loginUrl, params)
+
+ if "User Account Page" not in d : #Member Account
+ logging.info("Failed to login to URL %s as %s, or have no authorization to access the story" % (loginUrl, params['penname']))
+ raise exceptions.FailedToLogin(url,params['penname'])
+ return False
+ else:
+ return True
+
+ ## Getting the chapter list and the meta data, plus 'is adult' checking.
+ def extractChapterUrlsAndMetadata(self):
+ # index=1 makes sure we see the story chapter index. Some
+ # sites skip that for one-chapter stories.
+ url = self.url
+ logging.debug("URL: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+
+ if self.needToLoginCheck(data):
+ # need to log in for this one.
+ addurl="&ageconsent=ok"
+ self.performLogin(url)
+ data = self._fetchUrl(url+addurl)
+
+ 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 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+'/elysian/'+aut['href'])
+ self.story.setMetadata('author',aut.string)
+ aut.extract()
+
+ self.story.setMetadata('title',a.string[:(len(a.string)-3)])
+
+ # Find the chapters:
+ chapters=soup.find('select', {'name' : 'chapter'})
+ if chapters != None:
+ for chapter in chapters.findAll('option'):
+ self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/elysian/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+')}):
+ a = list.find('a', href=re.compile(r'viewstory.php\?sid='))
+ if a != None:
+ if 'viewstory.php?sid='+self.story.getMetadata('storyId') in a['href']:
+ break
+
+ # 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 ""
+
+
+ # Rated: NC-17
etc
+ labels = list.findAll('span', {'class' : 'classification'})
+ for labelspan in labels:
+ label = labelspan.text
+ value = labelspan.nextSibling
+
+ if 'Summary' in label:
+ ## Everything until the next span class='label'
+ svalue = ""
+ while not (defaultGetattr(value,'class') == 'classification' or "Chapters: " in stripHTML(value)):
+ 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[:len(value)-2])
+
+ if 'Word count' in label:
+ self.story.setMetadata('numWords', value)
+
+ if 'Categories' in label:
+ cats = labelspan.parent.findAll('a',href=re.compile(r'categories.php\?catid=\d+'))
+ for cat in cats:
+ self.story.addToList('category',cat.string)
+
+ if 'Characters' in label:
+ for char in value.string.split(', '):
+ if not 'None' in char:
+ self.story.addToList('characters',char)
+
+ if 'Genre' in label:
+ for genre in value.string.split(', '):
+ if not 'None' in genre:
+ self.story.addToList('genre',genre)
+
+ if 'Warnings' in label:
+ for warning in value.string.split(', '):
+ if not 'None' in warning:
+ self.story.addToList('warnings',warning)
+
+ 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))
+
+ try:
+ # Find Series name from series URL.
+ a = list.find('a', href=re.compile(r"series.php\?seriesid=\d+"))
+ series_name = a.string
+ series_url = 'http://'+self.host+'/elysian/'+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 ('viewstory.php?sid='+self.story.getMetadata('storyId')) in a['href']:
+ self.setSeries(series_name, i)
+ break
+ i+=1
+
+ except:
+ # I find it hard to care if the series parsing fails
+ pass
+
+ # grab the text for an individual chapter.
+ def getChapterText(self, url):
+
+ logging.debug('Getting chapter text from: %s' % url)
+
+ soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
+ selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
+
+ div = soup.find('div', {'id' : 'story'})
+
+ if None == div:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+
+ return self.utf8FromSoup(url,div)
diff --git a/fanficdownloader/adapters/adapter_pretendercentrecom.py b/fanficdownloader/adapters/adapter_pretendercentrecom.py
new file mode 100644
index 0000000..db200bc
--- /dev/null
+++ b/fanficdownloader/adapters/adapter_pretendercentrecom.py
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2012 Fanficdownloader team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import time
+import logging
+import re
+import urllib2
+
+from .. import BeautifulSoup as bs
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, makeDate
+
+def getClass():
+ return PretenderCenterComAdapter
+
+# Class name has to be unique. Our convention is camel case the
+# sitename with Adapter at the end. www is skipped.
+class PretenderCenterComAdapter(BaseSiteAdapter):
+
+ def __init__(self, config, url):
+ BaseSiteAdapter.__init__(self, config, url)
+
+ self.decode = ["Windows-1252",
+ "utf8"] # 1252 is a superset of iso-8859-1.
+ # Most sites that claim to be
+ # iso-8859-1 (and some that claim to be
+ # utf8) are really windows-1252.
+ self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
+ self.password = ""
+ self.is_adult=False
+
+ # get storyId from url--url validation guarantees query is only sid=1234
+ self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
+ logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
+
+ # normalized story URL.
+ self._setURL('http://' + self.getSiteDomain() + '/missingpieces/viewstory.php?sid='+self.story.getMetadata('storyId'))
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','ptdc')
+
+ # If all stories from the site fall into the same category,
+ # the site itself isn't likely to label them as such, so we
+ # do.
+ self.story.addToList("category","The Pretender")
+
+ # The date format will vary from site to site.
+ # http://docs.python.org/library/datetime.html#strftime-strptime-behavior
+ self.dateformat = "%d/%m/%y"
+
+ @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']
+
+ def getSiteExampleURLs(self):
+ return "http://"+self.getSiteDomain()+"/missingpieces/viewstory.php?sid=1234"
+
+ def getSiteURLPattern(self):
+ return re.escape("http://")+"(www\.)?"+re.escape(self.getSiteDomain()+"/missingpieces/viewstory.php?sid=")+r"\d+$"
+
+ ## Getting the chapter list and the meta data, plus 'is adult' checking.
+ def extractChapterUrlsAndMetadata(self):
+
+ if self.is_adult or self.getConfig("is_adult"):
+ # Weirdly, different sites use different warning numbers.
+ # If the title search below fails, there's a good chance
+ # you need a different number. print data at that point
+ # and see what the 'click here to continue' url says.
+ addurl = "&ageconsent=ok&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
+ logging.debug("URL: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+
+ m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
+ if m != None:
+ if self.is_adult or self.getConfig("is_adult"):
+ # We tried the default and still got a warning, so
+ # let's pull the warning number from the 'continue'
+ # link and reload data.
+ addurl = m.group(1)
+ # correct stupid & error in url.
+ addurl = addurl.replace("&","&")
+ url = self.url+'&index=1'+addurl
+ logging.debug("URL 2nd try: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+ else:
+ raise exceptions.AdultCheckRequired(self.url)
+
+ if "Access denied. This story has not been validated by the adminstrators of this site." in data:
+ raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
+
+ # use BeautifulSoup HTML parser to make everything easier to find.
+ soup = bs.BeautifulSoup(data)
+ # print data
+
+ # Now go hunting for all the meta data and the chapter list.
+
+ ## Title
+ a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
+ self.story.setMetadata('title',a.text)
+
+ # 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+'/missingpieces/'+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+'/missingpieces/'+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 ""
+
+
+ # 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.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')) # XXX
+ 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')) # XXX
+ 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+'/missingpieces/'+a['href']
+
+ # use BeautifulSoup HTML parser to make everything easier to find.
+ seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
+ # can't use ^viewstory...$ in case of higher rated stories with javascript href.
+ storyas = seriessoup.findAll('a', href=re.compile(r'viewstory.php\?sid=\d+'))
+ i=1
+ for a in storyas:
+ # skip 'report this' and 'TOC' links
+ if 'contact.php' not in a['href'] and 'index' not in a['href']:
+ if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
+ self.setSeries(series_name, i)
+ break
+ i+=1
+
+ except:
+ # I find it hard to care if the series parsing fails
+ pass
+
+ # grab the text for an individual chapter.
+ def getChapterText(self, url):
+
+ logging.debug('Getting chapter text from: %s' % url)
+
+ soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
+ selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
+
+ div = soup.findAll('div', {'id' : 'story'})[1]
+
+ if None == div:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+
+ return self.utf8FromSoup(url,div)
diff --git a/fanficdownloader/adapters/adapter_themasquenet.py b/fanficdownloader/adapters/adapter_themasquenet.py
new file mode 100644
index 0000000..5c14ed3
--- /dev/null
+++ b/fanficdownloader/adapters/adapter_themasquenet.py
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2012 Fanficdownloader team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import time
+import logging
+import re
+import urllib2
+
+from .. import BeautifulSoup as bs
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, makeDate
+
+def getClass():
+ return TheMasqueNetAdapter
+
+# Class name has to be unique. Our convention is camel case the
+# sitename with Adapter at the end. www is skipped.
+class TheMasqueNetAdapter(BaseSiteAdapter):
+
+ def __init__(self, config, url):
+ BaseSiteAdapter.__init__(self, config, url)
+
+ self.decode = ["Windows-1252",
+ "utf8"] # 1252 is a superset of iso-8859-1.
+ # Most sites that claim to be
+ # iso-8859-1 (and some that claim to be
+ # utf8) are really windows-1252.
+ self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
+ self.password = ""
+ self.is_adult=False
+
+ # get storyId from url--url validation guarantees query is only sid=1234
+ self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
+ logging.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
+
+ if self.parsedUrl.path.split('/',)[1] == 'wiktt':
+ self.story.addToList("category","Harry Potter")
+ self.story.setMetadata('section','/wiktt/efiction/')
+ self.dateformat = "%m/%d/%Y"
+ else:
+ self.story.addToList("category","Originals")
+ self.story.setMetadata('section','/efiction/')
+ self.dateformat = "%b %d, %Y"
+
+
+ # normalized story URL.
+ self._setURL('http://' + self.getSiteDomain() + self.story.getMetadata('section') + 'viewstory.php?sid='+self.story.getMetadata('storyId'))
+
+ # Each adapter needs to have a unique site abbreviation.
+ self.story.setMetadata('siteabbrev','msq')
+
+
+ @staticmethod # must be @staticmethod, don't remove it.
+ def getSiteDomain():
+ # The site domain. Does have www here, if it uses it.
+ return 'themasque.net'
+
+ @classmethod
+ def getAcceptDomains(cls):
+ return ['www.themasque.net','themasque.net'] #
+
+ def getSiteExampleURLs(self):
+ return "http://themasque.net/wiktt/efiction/viewstory.php?sid=1234 http://themasque.net/efiction/viewstory.php?sid=1234"
+
+ def getSiteURLPattern(self):
+ return re.escape("http://")+"(www\.)?"+re.escape(self.getSiteDomain())+"(/wiktt)?/efiction"+re.escape("/viewstory.php?sid=")+r"\d+$"
+
+ ## Login seems to be reasonably standard across eFiction sites.
+ def needToLoginCheck(self, data):
+ if 'Registered Users Only' in data \
+ or 'There is no such account on our website' in data \
+ or "That password doesn't match the one in our database" in data:
+ return True
+ else:
+ return False
+
+ def performLogin(self, url):
+ params = {}
+
+ if self.password:
+ params['penname'] = self.username
+ params['password'] = self.password
+ else:
+ params['penname'] = self.getConfig("username")
+ params['password'] = self.getConfig("password")
+ params['cookiecheck'] = '1'
+ params['submit'] = 'Submit'
+
+ loginUrl = 'http://' + self.getSiteDomain() + self.story.getMetadata('section') + 'user.php?action=login'
+ logging.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
+ params['penname']))
+
+ d = self._fetchUrl(loginUrl, params)
+
+ if "Member Account" not in d : #Member Account
+ logging.info("Failed to login to URL %s as %s" % (loginUrl,
+ params['penname']))
+ raise exceptions.FailedToLogin(url,params['penname'])
+ return False
+ else:
+ return True
+
+ ## Getting the chapter list and the meta data, plus 'is adult' checking.
+ def extractChapterUrlsAndMetadata(self):
+
+ if self.is_adult or self.getConfig("is_adult"):
+ # Weirdly, different sites use different warning numbers.
+ # If the title search below fails, there's a good chance
+ # you need a different number. print data at that point
+ # and see what the 'click here to continue' url says.
+ addurl = "&ageconsent=ok&warning=4"
+ else:
+ addurl=""
+
+ # index=1 makes sure we see the story chapter index. Some
+ # sites skip that for one-chapter stories.
+ url = self.url+addurl
+ logging.debug("URL: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+
+ if self.needToLoginCheck(data):
+ # need to log in for this one.
+ self.performLogin(url)
+ data = self._fetchUrl(url)
+
+ m = re.search(r"'viewstory.php\?sid=\d+((?:&ageconsent=ok)?&warning=\d+)'",data)
+ if m != None:
+ if self.is_adult or self.getConfig("is_adult"):
+ # We tried the default and still got a warning, so
+ # let's pull the warning number from the 'continue'
+ # link and reload data.
+ addurl = m.group(1)
+ # correct stupid & error in url.
+ addurl = addurl.replace("&","&")
+ url = self.url+'&index=1'+addurl
+ logging.debug("URL 2nd try: "+url)
+
+ try:
+ data = self._fetchUrl(url)
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ raise e
+ else:
+ raise exceptions.AdultCheckRequired(self.url)
+
+ if "Access denied. This story has not been validated by the adminstrators of this site." in data:
+ raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
+
+ # use BeautifulSoup HTML parser to make everything easier to find.
+ soup = bs.BeautifulSoup(data)
+ # print data
+
+ # Now go hunting for all the meta data and the chapter list.
+
+ ## Title
+ a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
+ self.story.setMetadata('title',a.string)
+
+ # Find authorid and URL from... author url.
+ a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
+ self.story.setMetadata('authorId',a['href'].split('=')[1])
+ self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
+ self.story.setMetadata('author',a.string)
+
+ # Find the chapters:
+ for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
+ # just in case there's tags, like in chapter titles.
+ self.chapterUrls.append((stripHTML(chapter),'http://'+self.host + self.story.getMetadata('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 ""
+
+# summary, rated, word count, categories, characters, genre, warnings, completed, published, updated, seires
+
+ # Rated: NC-17
etc
+ labels = soup.findAll('span',{'class':'label'})
+ for labelspan in labels:
+ value = labelspan.nextSibling
+ label = labelspan.text
+
+ 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))
+
+ # grab the text for an individual chapter.
+ def getChapterText(self, url):
+
+ logging.debug('Getting chapter text from: %s' % url)
+
+ soup = bs.BeautifulStoneSoup(self._fetchUrl(url),
+ selfClosingTags=('br','hr')) # otherwise soup eats the br/hr tags.
+
+ div = soup.find('div', {'id' : 'story'})
+
+ if None == div:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+
+ return self.utf8FromSoup(url,div)