import datetime from typing import List import FAAPI import Comment import User class Submission: """ A FurAffinity submission. """ def __init__(self, api, id: int): self._api = api self.id: int = id self._title: str = None self._author: User.User = None self._description: str = None self._tags: List[str] = None self._timestamp: datetime = None self._category: str = None self._subcategory: str = None self._theme: str = None self._species: str = None self._gender: str = None self._faves: int = None self._comments: List[Comment.Comment] = None self._views: int = None self._preview_url: str = None self._download_url: str = None self._faved: bool = None self._fave_url: str = None @property def url(self): if self.id and self.id > 0: return '{}/view/{}'.format(FAAPI.FA_BASE_URL, self.id) else: return None @property def title(self): if not self._title: self._get_submission() return self._title @property def author(self): if not self._author: self._get_submission() return self._author @property def description(self): if not self._description: self._get_submission() return self._description @property def tags(self): if not self._tags: self._get_submission() return self._tags @property def timestamp(self): if not self._timestamp: self._get_submission() return self._timestamp @property def category(self): if not self._category: self._get_submission() return self._category @property def subcategory(self): if not self._subcategory: self._get_submission() return self._subcategory @property def species(self): if not self._species: self._get_submission() return self._species @property def gender(self): if not self._gender: self._get_submission() return self._gender @property def faves(self): if not self._faves: self._get_submission() return self._faves @property def comments(self): if not self._comments: self._get_submission() return self._comments @property def views(self): if not self._views: self._get_submission() return self._views @property def preview_url(self): if not self._preview_url: self._get_submission() return self._preview_url @property def download_url(self): if not self._download_url: self._get_submission() return self._download_url @property def faved(self): if self._faved is None: self._get_submission() return self._faved def fave(self): if self.faved is False: # Usage of the faved property is deliberate to ensure _get_submission() has run self._toggle_fave() def unfave(self): if self.faved is True: # Usage of the faved property is deliberate to ensure _get_submission() has run self._toggle_fave() def _get_submission(self): soup = self._api.get_soup('{}/view/{}'.format(FAAPI.FA_BASE_URL, self.id)) # Parse all the easy stuff self._title = soup.select('.submission-title')[0].text.strip() self._author = User.User.new_or_cached(self._api, soup.select('.submission-id-sub-container a')[0].text.strip()) self._description = soup.select('.submission-description')[0].text.strip() self._tags = [tag.text.strip() for tag in soup.select('.submission-sidebar .tags')] self._timestamp = datetime.datetime.strptime(soup.select('.popup_date')[0].get('title'), '%b %d, %Y %H:%M %p') # e.g. Nov 26, 2019 02:47 PM self._category = soup.select('.category-name')[0].text.strip() self._subcategory = soup.select('.type-name')[0].text.strip() self._species = soup.select('.info div:nth-of-type(2) span')[0].text.strip() self._gender = soup.select('.info div:nth-of-type(3) span')[0].text.strip() self._faves = int(soup.select('.favorites .font-large')[0].text.strip()) self._views = int(soup.select('.views .font-large')[0].text.strip()) self._preview_url = 'https:' + soup.select('#submissionImg')[0].get('data-preview-src') self._download_url = 'https:' + soup.select('#submissionImg')[0].get('data-fullview-src') self._faved = '-' in soup.select('.fav')[0].text self._fave_url = FAAPI.FA_BASE_URL + soup.select('.fav a')[0].get('href') # Parse comments self._comments = [] for comment_element in soup.select('.comment_container'): # Parse all the easy stuff comment = Comment.Comment(self, int(comment_element.select('.comment_anchor')[0].get('id')[4:])) # Skip the 'cid:' in the ID e.g. cid:142785397 # TODO: Comment ancestry # Toggle hidden status if comment_element.select('.comment-deleted'): # Deleted by its owner, set flags and skip remaining attributes comment.hidden_by_author = True comment.hidden_by_page_owner = False self._comments.append(comment) continue elif 'collapsed_height' in comment_element.get('class'): # Deleted by the page owner, set flags and skip remaining attributes comment.hidden_by_author = False comment.hidden_by_page_owner = True self._comments.append(comment) continue else: comment.hidden_by_author = False comment.hidden_by_page_owner = False # Parse content from non-hidden comments comment.author = User.User.new_or_cached(self._api, comment_element.select('.comment_username')[0].text.strip()) comment.timestamp = datetime.datetime.fromtimestamp(int(comment_element.get('data-timestamp'))) # FIXME: Needs to account for timezone difference since these are server-local epochs. Seems to be hosted in New York's TZ self._comments.append(comment) def _toggle_fave(self): soup = self._api.get_soup(self._fave_url) self._faved = '-' in soup.select('.fav')[0].text self._fave_url = FAAPI.FA_BASE_URL + soup.select('.fav a')[0].get('href') def __str__(self): return '{} - {} ({}/view/{})'.format(self.author, self.title, self.url)