diff --git a/CHANGELOG.md b/CHANGELOG.md index 25864cb..32c580e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v3.12.0 + +### New Features + +* Add `Journal.rating` and `JournalPartial.rating` properties + +### Fixes + +* Fix parsing errors due to FurAffinity's update on 2025-11-28 + ## v3.11.14 ### Fixes diff --git a/README.md b/README.md index 961e5f3..e04446b 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,7 @@ This object contains partial information gathered when parsing a journals folder * `id: int` journal ID * `title: str` journal title +* `rating: str` journal rating * `date: datetime` upload date as a [`datetime` object](https://docs.python.org/3/library/datetime.html) (defaults to timestamp 0) * `author: UserPartial` journal author (filled only if the journal is parsed from a `bs4.BeautifulSoup` page) @@ -321,6 +322,7 @@ as `JournalPartial` with the addition of comments: * `id: int` journal ID * `title: str` journal title +* `rating: str` journal rating * `date: datetime` upload date as a [`datetime` object](https://docs.python.org/3/library/datetime.html) (defaults to timestamp 0) * `author: UserPartial` journal author (filled only if the journal is parsed from a `bs4.BeautifulSoup` page) diff --git a/faapi/__version__.py b/faapi/__version__.py index 53c71cd..d1a7f1e 100644 --- a/faapi/__version__.py +++ b/faapi/__version__.py @@ -1 +1 @@ -__version__ = "3.11.14" +__version__ = "3.12.0" diff --git a/faapi/journal.py b/faapi/journal.py index 7b31a53..82632f7 100644 --- a/faapi/journal.py +++ b/faapi/journal.py @@ -27,6 +27,7 @@ class JournalBase: def __init__(self): self.id: int = 0 self.title: str = "" + self.rating: str = "" self.date: datetime = datetime.fromtimestamp(0) self.author: UserPartial = UserPartial() self.stats: JournalStats = JournalStats(0) @@ -74,6 +75,7 @@ def __le__(self, other) -> bool: def __iter__(self): yield "id", self.id yield "title", self.title + yield "rating", self.rating yield "date", self.date yield "author", dict(self.author) yield "stats", self.stats._asdict() @@ -140,6 +142,7 @@ def parse(self, journal_tag: Optional[Union[Tag, BeautifulSoup]] = None): # noinspection DuplicatedCode self.id = parsed["id"] self.title = parsed["title"] + self.rating = parsed["rating"] self.author.name = parsed.get("user_name", "") self.author.display_name = parsed.get("user_display_name", "") self.author.status = parsed.get("user_status", "") @@ -220,6 +223,7 @@ def parse(self, journal_page: Optional[Union[Tag, BeautifulSoup]] = None): # noinspection DuplicatedCode self.id = parsed["id"] self.title = parsed["title"] + self.rating = parsed["rating"] self.author.name = parsed["user_info"]["name"] self.author.display_name = parsed["user_info"]["display_name"] self.author.status = parsed["user_info"]["status"] diff --git a/faapi/parse.py b/faapi/parse.py index 443aa4c..b0b605e 100644 --- a/faapi/parse.py +++ b/faapi/parse.py @@ -358,18 +358,21 @@ def parse_loggedin_user(page: BeautifulSoup) -> Optional[str]: def parse_journal_section(section_tag: Tag) -> dict[str, Any]: id_: int = int(section_tag.attrs.get("id", "00000")[4:]) tag_title: Optional[Tag] = section_tag.select_one("h2") + tag_rating: Optional[Tag] = section_tag.select_one("span.c-contentRating--general") tag_date: Optional[Tag] = section_tag.select_one("div.section-header span.popup_date") tag_content: Optional[Tag] = section_tag.select_one("div.journal-body") tag_comments: Optional[Tag] = section_tag.select_one("div.section-footer > a > span") assert id_ != 0, _raise_exception(ParsingError("Missing ID")) assert tag_title is not None, _raise_exception(ParsingError("Missing title tag")) + assert tag_rating is not None, _raise_exception(ParsingError("Missing rating tag")) assert tag_date is not None, _raise_exception(ParsingError("Missing date tag")) assert tag_content is not None, _raise_exception(ParsingError("Missing content tag")) assert tag_comments is not None, _raise_exception(ParsingError("Missing comments tag")) # noinspection DuplicatedCode title: str = tag_title.text.strip() + rating: str = tag_rating.text.strip() date: datetime = parse_date( get_attr(tag_date, "title").strip() if match(r"^[A-Za-z]+ \d+,.*$", get_attr(tag_date, "title")) @@ -382,6 +385,7 @@ def parse_journal_section(section_tag: Tag) -> dict[str, Any]: return { "id": id_, "title": title, + "rating": rating, "date": date, "content": content, "mentions": mentions, @@ -392,8 +396,9 @@ def parse_journal_section(section_tag: Tag) -> dict[str, Any]: def parse_journal_page(journal_page: BeautifulSoup) -> dict[str, Any]: user_info: dict[str, str] = parse_user_folder(journal_page) tag_id: Optional[Tag] = journal_page.select_one("meta[property='og:url']") - tag_title: Optional[Tag] = journal_page.select_one("h2.journal-title") - tag_date: Optional[Tag] = journal_page.select_one("div.content div.section-header span.popup_date") + tag_title: Optional[Tag] = journal_page.select_one("#c-journalTitleTop__subject h3") + tag_rating: Optional[Tag] = journal_page.select_one("#c-journalTitleTop__contentRating") + tag_date: Optional[Tag] = journal_page.select_one("div.content div.section-header span.popup_date[data-time]") tag_header: Optional[Tag] = journal_page.select_one("div.journal-header") tag_footer: Optional[Tag] = journal_page.select_one("div.journal-footer") tag_content: Optional[Tag] = journal_page.select_one("div.journal-content") @@ -401,6 +406,7 @@ def parse_journal_page(journal_page: BeautifulSoup) -> dict[str, Any]: assert tag_id is not None, _raise_exception(ParsingError("Missing ID tag")) assert tag_title is not None, _raise_exception(ParsingError("Missing title tag")) + assert tag_rating is not None, _raise_exception(ParsingError("Missing rating tag")) assert tag_date is not None, _raise_exception(ParsingError("Missing date tag")) assert tag_content is not None, _raise_exception(ParsingError("Missing content tag")) assert tag_comments is not None, _raise_exception(ParsingError("Missing comments tag")) @@ -408,11 +414,8 @@ def parse_journal_page(journal_page: BeautifulSoup) -> dict[str, Any]: id_: int = int(tag_id.attrs.get("content", "0").strip("/").split("/")[-1]) # noinspection DuplicatedCode title: str = tag_title.text.strip() - date: datetime = parse_date( - get_attr(tag_date, "title").strip() - if match(r"^[A-Za-z]+ \d+,.*$", get_attr(tag_date, "title")) - else tag_date.text.strip() - ) + rating: str = tag_rating.text.strip() + date: datetime = datetime.fromtimestamp(int(tag_date.attrs["data-time"])) header: str = clean_html(inner_html(tag_header)) if tag_header else "" footer: str = clean_html(inner_html(tag_footer)) if tag_footer else "" content: str = clean_html(inner_html(tag_content)) @@ -425,6 +428,7 @@ def parse_journal_page(journal_page: BeautifulSoup) -> dict[str, Any]: "user_info": user_info, "id": id_, "title": title, + "rating": rating, "date": date, "content": content, "header": header, @@ -499,12 +503,12 @@ def parse_submission_page(sub_page: BeautifulSoup) -> dict[str, Any]: tag_title: Optional[Tag] = tag_sub_info.select_one("div.submission-title") tag_author: Optional[Tag] = sub_page.select_one("div.submission-id-container") - tag_date: Optional[Tag] = sub_page.select_one("div.submission-id-container span.popup_date") + tag_date: Optional[Tag] = sub_page.select_one("div.submission-id-container span.popup_date[data-time]") tag_tags: list[Tag] = sub_page.select('section.tags-row a[href^="/"]') tag_views: Optional[Tag] = sub_page.select_one("div.views span") tag_comment_count: Optional[Tag] = sub_page.select_one("section.stats-container div.comments span") tag_favorites: Optional[Tag] = sub_page.select_one("div.favorites span") - tag_rating: Optional[Tag] = sub_page.select_one("div.rating span.rating-box") + tag_rating: Optional[Tag] = sub_page.select_one("div.rating span.inline") tag_type: Optional[Tag] = sub_page.select_one("div#submission_page[class^='page-content-type']") tag_fav: Optional[Tag] = sub_page.select_one("div.fav > a") tag_info: Optional[Tag] = sub_page.select_one("section.info.text") @@ -543,11 +547,7 @@ def parse_submission_page(sub_page: BeautifulSoup) -> dict[str, Any]: id_: int = int(get_attr(tag_id, "content").strip("/").split("/")[-1]) title: str = tag_title.text.strip() - date: datetime = parse_date( - get_attr(tag_date, "title").strip() - if match(r"^[A-Za-z]+ \d+,.*$", get_attr(tag_date, "title")) - else tag_date.text.strip() - ) + date: datetime = datetime.fromtimestamp(int(tag_date.attrs["data-time"])) tags: list[str] = [t.text.strip() for t in tag_tags] category: str = "" if tag_category1: diff --git a/pyproject.toml b/pyproject.toml index 7700f03..cd2eda6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "faapi" -version = "3.11.14" +version = "3.12.0" description = "Python module to implement API-like functionality for the FurAffinity.net website." authors = ["Matteo Campinoti "] license = "EUPL-1.2"