Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion faapi/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.11.14"
__version__ = "3.12.0"
4 changes: 4 additions & 0 deletions faapi/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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", "")
Expand Down Expand Up @@ -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"]
Expand Down
28 changes: 14 additions & 14 deletions faapi/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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,
Expand All @@ -392,27 +396,26 @@ 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")
tag_comments: Optional[Tag] = journal_page.select_one("div.section-footer > span")

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"))

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))
Expand All @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <matteo.campinoti94@gmail.com>"]
license = "EUPL-1.2"
Expand Down