From 4a228e08a9bbf16dcf65618d9beaa8d516c80173 Mon Sep 17 00:00:00 2001
From: Jim Miller
Date: Sat, 5 Jan 2013 11:47:53 -0600
Subject: [PATCH] Add site restrictedsection.org, update web version/comments
---
app.yaml | 2 +-
defaults.ini | 10 +
fanficdownloader/adapters/__init__.py | 2 +-
.../adapters/adapter_restrictedsectionorg.py | 235 ++++++++++++++++++
index.html | 41 ++-
5 files changed, 279 insertions(+), 11 deletions(-)
create mode 100644 fanficdownloader/adapters/adapter_restrictedsectionorg.py
diff --git a/app.yaml b/app.yaml
index 22c205e..c6ba9dd 100644
--- a/app.yaml
+++ b/app.yaml
@@ -1,6 +1,6 @@
# ffd-retief-hrd fanfictiondownloader
application: fanfictiondownloader
-version: 4-4-36
+version: 4-4-37
runtime: python27
api_version: 1
threadsafe: true
diff --git a/defaults.ini b/defaults.ini
index f3aa3aa..5f672c7 100644
--- a/defaults.ini
+++ b/defaults.ini
@@ -1144,6 +1144,16 @@ extracategories:Queer as Folk
## this should go in your personal.ini, not defaults.ini.
#is_adult:true
+[www.restrictedsection.org]
+extracategories:Harry Potter
+extragenres:Erotica
+## Some sites require login (or login for some rated stories) The
+## program can prompt you, or you can save it in config. In
+## commandline version, this should go in your personal.ini, not
+## defaults.ini.
+#username:YourName
+#password:yourpassword
+
[www.scarvesandcoffee.net]
## Site dedicated to these categories/characters/ships
extracategories:Glee
diff --git a/fanficdownloader/adapters/__init__.py b/fanficdownloader/adapters/__init__.py
index 22e3b5d..3fcc216 100644
--- a/fanficdownloader/adapters/__init__.py
+++ b/fanficdownloader/adapters/__init__.py
@@ -106,11 +106,11 @@ import adapter_indeathnet
import adapter_jlaunlimitedcom
import adapter_qafficcom
import adapter_efpfanficnet
-#import adapter_buffynfaithnet
import adapter_potterficscom
import adapter_efictionestelielde
import adapter_dotmoonnet
import adapter_pommedesangcom
+import adapter_restrictedsectionorg
## This bit of complexity allows adapters to be added by just adding
## importing. It eliminates the long if/else clauses we used to need
diff --git a/fanficdownloader/adapters/adapter_restrictedsectionorg.py b/fanficdownloader/adapters/adapter_restrictedsectionorg.py
new file mode 100644
index 0000000..692183d
--- /dev/null
+++ b/fanficdownloader/adapters/adapter_restrictedsectionorg.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2013 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
+logger = logging.getLogger(__name__)
+import re
+import urllib2
+import cookielib as cl
+from datetime import datetime
+import json
+
+from .. import BeautifulSoup as bs
+from ..htmlcleanup import stripHTML
+from .. import exceptions as exceptions
+
+from base_adapter import BaseSiteAdapter, makeDate
+
+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'))
+ logger.debug("storyId: (%s)"%self.story.getMetadata('storyId'))
+ # normalized story URL.
+ self._setURL('http://' + self.getSiteDomain() + '/' + m.group('filestory') + '.php?' + m.group('filestory') + '=' + self.story.getMetadata('storyId'))
+ logger.debug("storyUrl: (%s)"%self.story.getMetadata('storyUrl'))
+ 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'
+
+ def getSiteExampleURLs(self):
+ return "http://"+self.getSiteDomain()+"/story.php?story=1234 http://"+self.getSiteDomain()+"/file.php?file=1234"
+
+ def getSiteURLPattern(self):
+ return re.escape("http://"+self.getSiteDomain())+r"/(?Pfile|story).php\?(file|story)=(?P\d+)$"
+
+ ## Getting the chapter list and the meta data, plus 'is adult' checking.
+ def extractChapterUrlsAndMetadata(self):
+
+ url = self.url
+ logger.debug("URL: "+url)
+
+ # one-shot stories use file url instead of story. 'Luckily',
+ # we don't have to worry about one-shots becoming
+ # multi-chapter because ressec is frozen. Still need 'story'
+ # url for metadata, however.
+ try:
+ if 'file' in url:
+ data = self._postUrlUP(url)
+ soup = bs.BeautifulSoup(data)
+ storya = soup.find('a',href=re.compile(r"^story.php\?story=\d+"))
+ url = 'http://'+self.host+'/'+storya['href'].split('&')[0] # strip rs_session
+
+ logger.debug("metadata URL: "+url)
+ data = self._fetchUrl(url)
+ # print data
+ except urllib2.HTTPError, e:
+ if e.code == 404:
+ raise exceptions.StoryDoesNotExist(self.url)
+ else:
+ 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)
+
+ # check user/pass on a chapter for multi-chapter
+ if 'file' not in self.url:
+ self._postUrlUP('http://'+self.host+'/'+soup.find('a', href=re.compile(r"^file.php\?file=\d+"))['href'])
+
+ ## Title
+ h2 = soup.find('h2')
+
+ # 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',a.string)
+
+ # title, remove byauthorname.
+ self.story.setMetadata('title',h2.text[:h2.text.index("by"+a.string)])
+
+ dates = soup.findAll('span', {'class':'date'})
+ if dates: # only for multi-chapter
+ self.story.setMetadata('datePublished', makeDate(stripHTML(dates[0]), self.dateformat))
+ self.story.setMetadata('dateUpdated', makeDate(stripHTML(dates[-1]), self.dateformat))
+
+ words = soup.findAll('span', {'class':'size'})
+ wordcount=0
+ for w in words:
+ wordcount = wordcount + int(w.string[:-6].replace(',',''))
+
+ 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.
+
+ if 'Updated' in label: # one-shots only.
+ print "value:%s"%value
+ value.find('sup').extract() # remove 'st', 'nd', 'th' ordinals
+ print "value:%s"%value
+ date = makeDate(stripHTML(value), '%d %B %Y') # full month name
+ self.story.setMetadata('datePublished', date)
+
+ 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))
+ else: # multi-chapter
+ # Find the chapters: 'library_storyview.php?chapterid=3
+ chapters=soup.findAll('a', href=re.compile(r"^file.php\?file=\d+"))
+ if len(chapters)==0:
+ raise exceptions.FailedToDownload(self.getSiteDomain() +" says: No chapters found.")
+ else:
+ for chapter in chapters:
+ chhref = chapter['href'].split('&')[0] # strip rs_session
+ # just in case there's tags, like in chapter titles.
+ self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chhref))
+
+ self.story.setMetadata('numChapters',len(self.chapterUrls))
+
+
+
+ def _postUrlUP(self, url):
+ params = {}
+ if self.password:
+ params['username'] = self.username
+ params['password'] = self.password
+ else:
+ params['username'] = self.getConfig("username")
+ params['password'] = self.getConfig("password")
+ params['accept.x'] = 1
+ params['accept.y'] = 1
+
+ data = self._postUrl(url, params)
+ if "I certify that I am over the age of 18 and that accessing the following story will not violate the laws of my country or local ordinances." in data:
+ raise exceptions.FailedToLogin(url,params['username'])
+ return data
+
+ # grab the text for an individual chapter.
+ def getChapterText(self, url):
+
+ logger.debug('Getting chapter text from: %s' % url)
+
+ data = self._postUrlUP(url)
+ soup = bs.BeautifulSoup(data)
+
+ div = soup.find('td',{'id':'page_content'})
+ div.name='div'
+
+ if None == div:
+ raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
+
+ ## Remove stuff from page_content
+
+ # Remove all tags before the first
after class=info table (including hr)
+ hr = div.find('table',{'class':'info'}).findNext('hr')
+ for tag in hr.findAllPrevious():
+ tag.extract()
+ hr.extract()
+
+ # Remove all tags after the last
(including hr)
+ hr = div.findAll('hr')[-1]
+ for tag in hr.findAllNext():
+ tag.extract()
+ hr.extract()
+
+ return self.utf8FromSoup(url,div)
diff --git a/index.html b/index.html
index 9a0a573..142e457 100644
--- a/index.html
+++ b/index.html
@@ -54,17 +54,17 @@
much easier.
- New Site:
-
- New Spanish language site supported: www.potterfics.com (Thanks Dan!)
-
- Fixes:
+ Changes:
- - Remove site www.yourfanfiction.com--it's been gone for awhile.
- - Correct AO3 extra metadata freefromtags to freeformtags. freefromtags will continue to work for those already using it.
- - Workaround for fimfiction.net's API issue with non-viewable chapters given.
+ - New site: www.dotmoon.net (Thanks Ida!)
+ - New site: efiction.esteliel.de (Thanks Ida!)
+ - New site: pommedesang.com (Thanks Ida!)
+ - New Spanish language site supported: www.potterfics.com (Thanks Dan!)
+ - New site: www.restrictedsection.org (Yes, this site has been frozen since March 2009.)
+ - More workarounds for fimfiction.net's API issues.
+ FFDL now supports over 80 different sites.
Questions? Check out our
@@ -74,7 +74,7 @@
If you have any problems with this application, please
report them in
the FanFictionDownLoader Google Group. The
- Previous Version is also available for you to use if necessary.
+ Previous Version is also available for you to use if necessary.