PageRenderTime 123ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/fanficdownloader/adapters/adapter_twcslibrarynet.py

https://code.google.com/p/fanficdownloader/
Python | 272 lines | 245 code | 8 blank | 19 comment | 1 complexity | bf2fd31127107b059e68145fce4b1237 MD5 | raw file
Possible License(s): MIT
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2011 Fanficdownloader team
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # Software: eFiction
  17. import time
  18. import logging
  19. logger = logging.getLogger(__name__)
  20. import re
  21. import urllib
  22. import urllib2
  23. from .. import BeautifulSoup as bs
  24. from ..htmlcleanup import stripHTML
  25. from .. import exceptions as exceptions
  26. from base_adapter import BaseSiteAdapter, makeDate
  27. class TheWritersCoffeeShopComSiteAdapter(BaseSiteAdapter):
  28. def __init__(self, config, url):
  29. BaseSiteAdapter.__init__(self, config, url)
  30. self.story.setMetadata('siteabbrev','twcs')
  31. self.decode = ["Windows-1252",
  32. "utf8"] # 1252 is a superset of iso-8859-1.
  33. # Most sites that claim to be
  34. # iso-8859-1 (and some that claim to be
  35. # utf8) are really windows-1252.
  36. self.username = "NoneGiven" # if left empty, site doesn't return any message at all.
  37. self.password = ""
  38. self.is_adult=False
  39. # get storyId from url--url validation guarantees query is only sid=1234
  40. self.story.setMetadata('storyId',self.parsedUrl.query.split('=',)[1])
  41. # normalized story URL.
  42. self._setURL('http://' + self.getSiteDomain() + '/viewstory.php?sid='+self.story.getMetadata('storyId'))
  43. self.dateformat = "%d %b %Y"
  44. @classmethod
  45. def getAcceptDomains(cls):
  46. return ['thewriterscoffeeshop.com','twcslibrary.net']
  47. @staticmethod
  48. def getSiteDomain():
  49. return 'www.twcslibrary.net'
  50. @classmethod
  51. def getSiteExampleURLs(cls):
  52. return "http://"+cls.getSiteDomain()+"/viewstory.php?sid=1234"
  53. def getSiteURLPattern(self):
  54. return re.escape("http://")+"(www.)?("+'|'.join(self.getAcceptDomains())+")/(library/)?"+re.escape("viewstory.php?sid=")+r"\d+$"
  55. def needToLoginCheck(self, data):
  56. if 'Registered Users Only' in data \
  57. or 'There is no such account on our website' in data \
  58. or "That password doesn't match the one in our database" in data:
  59. return True
  60. else:
  61. return False
  62. def performLogin(self, url):
  63. params = {}
  64. if self.password:
  65. params['penname'] = self.username
  66. params['password'] = self.password
  67. else:
  68. params['penname'] = self.getConfig("username")
  69. params['password'] = self.getConfig("password")
  70. params['cookiecheck'] = '1'
  71. params['submit'] = 'Submit'
  72. loginUrl = 'http://' + self.getSiteDomain() + '/user.php?action=login'
  73. logger.debug("Will now login to URL (%s) as (%s)" % (loginUrl,
  74. params['penname']))
  75. d = self._fetchUrl(loginUrl, params)
  76. if "Member Account" not in d : #Member Account
  77. logger.info("Failed to login to URL %s as %s" % (loginUrl,
  78. params['penname']))
  79. raise exceptions.FailedToLogin(url,params['penname'])
  80. return False
  81. else:
  82. return True
  83. def extractChapterUrlsAndMetadata(self):
  84. if self.is_adult or self.getConfig("is_adult"):
  85. addurl = "&ageconsent=ok&warning=3"
  86. else:
  87. addurl=""
  88. url = self.url+'&index=1'+addurl
  89. logger.debug("URL: "+url)
  90. try:
  91. data = self._fetchUrl(url)
  92. except urllib2.HTTPError, e:
  93. if e.code == 404:
  94. raise exceptions.StoryDoesNotExist(self.url)
  95. else:
  96. raise e
  97. if self.needToLoginCheck(data):
  98. # need to log in for this one.
  99. self.performLogin(url)
  100. data = self._fetchUrl(url)
  101. if "Age Consent Required" in data:
  102. raise exceptions.AdultCheckRequired(self.url)
  103. if "Access denied. This story has not been validated by the adminstrators of this site." in data:
  104. raise exceptions.FailedToDownload(self.getSiteDomain() +" says: Access denied. This story has not been validated by the adminstrators of this site.")
  105. # problems with some stories, but only in calibre. I suspect
  106. # issues with different SGML parsers in python. This is a
  107. # nasty hack, but it works.
  108. data = data[data.index("<body"):]
  109. # use BeautifulSoup HTML parser to make everything easier to find.
  110. soup = bs.BeautifulSoup(data)
  111. ## Title
  112. a = soup.find('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"$"))
  113. self.story.setMetadata('title',stripHTML(a))
  114. # Find authorid and URL from... author url.
  115. a = soup.find('a', href=re.compile(r"viewuser.php\?uid=\d+"))
  116. self.story.setMetadata('authorId',a['href'].split('=')[1])
  117. self.story.setMetadata('authorUrl','http://'+self.host+'/'+a['href'])
  118. self.story.setMetadata('author',a.string)
  119. # Find the chapters:
  120. for chapter in soup.findAll('a', href=re.compile(r'viewstory.php\?sid='+self.story.getMetadata('storyId')+"&chapter=\d+$")):
  121. # just in case there's tags, like <i> in chapter titles.
  122. self.chapterUrls.append((stripHTML(chapter),'http://'+self.host+'/'+chapter['href']+addurl))
  123. self.story.setMetadata('numChapters',len(self.chapterUrls))
  124. def defaultGetattr(d,k):
  125. try:
  126. return d[k]
  127. except:
  128. return ""
  129. # <span class="label">Rated:</span> NC-17<br /> etc
  130. labels = soup.findAll('span',{'class':'label'})
  131. for labelspan in labels:
  132. value = labelspan.nextSibling
  133. label = labelspan.string
  134. if 'Summary' in label:
  135. ## Everything until the next span class='label'
  136. svalue = ""
  137. while not defaultGetattr(value,'class') == 'label':
  138. svalue += str(value)
  139. # poor HTML(unclosed <p> for one) can cause run on
  140. # over the next label.
  141. if '<span class="label">' in svalue:
  142. svalue = svalue[0:svalue.find('<span class="label">')]
  143. break
  144. else:
  145. value = value.nextSibling
  146. self.setDescription(url,svalue)
  147. if 'Rated' in label:
  148. self.story.setMetadata('rating', value)
  149. if 'Word count' in label:
  150. self.story.setMetadata('numWords', value)
  151. if 'Categories' in label:
  152. cats = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=categories'))
  153. catstext = [cat.string for cat in cats]
  154. for cat in catstext:
  155. self.story.addToList('category',cat.string)
  156. if 'Characters' in label:
  157. chars = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=characters'))
  158. charstext = [char.string for char in chars]
  159. for char in charstext:
  160. self.story.addToList('characters',char.string)
  161. if 'Genre' in label:
  162. genres = labelspan.parent.findAll('a',href=re.compile(r'browse.php\?type=class'))
  163. genrestext = [genre.string for genre in genres]
  164. self.genre = ', '.join(genrestext)
  165. for genre in genrestext:
  166. self.story.addToList('genre',genre.string)
  167. if 'Completed' in label:
  168. if 'Yes' in value:
  169. self.story.setMetadata('status', 'Completed')
  170. else:
  171. self.story.setMetadata('status', 'In-Progress')
  172. if 'Published' in label:
  173. self.story.setMetadata('datePublished', makeDate(stripHTML(value), self.dateformat))
  174. if 'Updated' in label:
  175. # there's a stray [ at the end.
  176. #value = value[0:-1]
  177. self.story.setMetadata('dateUpdated', makeDate(stripHTML(value), self.dateformat))
  178. try:
  179. # Find Series name from series URL.
  180. a = soup.find('a', href=re.compile(r"viewseries.php\?seriesid=\d+"))
  181. series_name = a.string
  182. series_url = 'http://'+self.host+'/'+a['href']
  183. # use BeautifulSoup HTML parser to make everything easier to find.
  184. seriessoup = bs.BeautifulSoup(self._fetchUrl(series_url))
  185. storyas = seriessoup.findAll('a', href=re.compile(r'^viewstory.php\?sid=\d+$'))
  186. i=1
  187. for a in storyas:
  188. if a['href'] == ('viewstory.php?sid='+self.story.getMetadata('storyId')):
  189. self.setSeries(series_name, i)
  190. self.story.setMetadata('seriesUrl',series_url)
  191. break
  192. i+=1
  193. except:
  194. # I find it hard to care if the series parsing fails
  195. pass
  196. def getChapterText(self, url):
  197. logger.debug('Getting chapter text from: %s' % url)
  198. data = self._fetchUrl(url)
  199. # problems with some stories, but only in calibre. I suspect
  200. # issues with different SGML parsers in python. This is a
  201. # nasty hack, but it works.
  202. data = data[data.index("<body"):]
  203. chapter=bs.BeautifulSoup('<div class="story"></div>')
  204. soup = bs.BeautifulSoup(data)
  205. found=False
  206. for div in soup.findAll('div'):
  207. if div.has_key('class') and div['class'] == 'notes':
  208. chapter.append(div)
  209. if div.has_key('id') and div['id'] == 'story':
  210. chapter.append(div)
  211. found=True
  212. if not found:
  213. raise exceptions.FailedToDownload("Error downloading Chapter: %s! Missing required element!" % url)
  214. return self.utf8FromSoup(url,chapter)
  215. def getClass():
  216. return TheWritersCoffeeShopComSiteAdapter