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
25 changes: 10 additions & 15 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,20 @@ on:
workflow_dispatch:

env:
PYTHON_VERSION: 3.11.0
POETRY_VERSION: 1.3.1
MIN_PYTHON_VERSION: 3.9.25
PYTHON_VERSION: 3.13.17
POETRY_VERSION: 2.5.1

jobs:
flake8:
if: github.event_name != 'schedule'
name: Flake8
runs-on: ubuntu-latest
strategy:
matrix:
python: [ "3.11", "3.10", "3.9" ]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python }}
python-version: ${{ env.MIN_PYTHON_VERSION }}
- name: Run pip install
run: python -m pip install 'flake8>=5.0.4'
- name: Style Test
Expand All @@ -34,15 +32,12 @@ jobs:
mypy:
if: github.event_name != 'schedule'
name: mypy
runs-on: ubuntu-latest
strategy:
matrix:
python: [ "3.11", "3.10", "3.9" ]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python }}
python-version: ${{ env.MIN_PYTHON_VERSION }}
- name: Run pip install
run: python -m pip install 'mypy>=0.991' 'pytest>=7.2.0' 'types-beautifulsoup4>=4.11.6'
- name: Types test
Expand All @@ -56,7 +51,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: abatilo/actions-poetry@v2.0.0
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## v3.13.0

### New Features

* Add `User.id: UUID` property

### Fixes

* Fix parsing of user pages
* Fix issue [#37](https://github.com/FurryCoders/FAAPI/issues/37)

## v3.12.7

### Fixes
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ If no `user_tag` is passed then the object fields will remain at their default -

The main class storing all of a user's metadata.

* `id: UUID` unique Fur Affinity user ID
* `name: str` display name with capital letters and extra characters such as "_"
* `status: str` user status (~, !, etc.)
* `title: str` the user title as it appears on their userpage
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.12.7"
__version__ = "3.13.0"
21 changes: 17 additions & 4 deletions faapi/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Optional
from typing import Union
from urllib.parse import quote
from uuid import UUID

from bbcode import Parser as BBCodeParser # type:ignore
from bs4 import BeautifulSoup
Expand Down Expand Up @@ -632,24 +633,35 @@ def parse_submission_page(sub_page: BeautifulSoup) -> dict[str, Any]:
def parse_user_header(user_header: Tag) -> dict[str, Any]:
tag_user_name: Optional[Tag] = user_header.select_one("a.c-usernameBlock__userName")
tag_user_display_name: Optional[Tag] = user_header.select_one("a.c-usernameBlock__displayName")
tag_title_join_date: Optional[Tag] = user_header.select_one("userpage-nav-user-details span.user-title")
tag_user_title: Optional[Tag] = user_header.select_one("userpage-nav-user-details span.user-title")
tag_avatar: Optional[Tag] = user_header.select_one("userpage-nav-avatar img")

assert tag_user_name is not None, _raise_exception(ParsingError("Missing user name tag"))
assert tag_user_display_name is not None, _raise_exception(ParsingError("Missing user display name tag"))
assert tag_title_join_date is not None, _raise_exception(ParsingError("Missing join date tag"))
assert tag_user_title is not None, _raise_exception(ParsingError("Missing user title tag"))
assert tag_avatar is not None, _raise_exception(ParsingError("Missing user icon tag"))

tag_join_date: Optional[Tag] = tag_user_title.select_one("span.popup_date[data-time]")
tag_unique_id: Optional[Tag] = tag_user_title.select_one('a[href^="/user/id:"]')

assert tag_join_date is not None, _raise_exception(ParsingError("Missing join date tag"))
assert tag_unique_id is not None, _raise_exception(ParsingError("Missing unique id tag"))

tag_user_symbol: Optional[Tag] = tag_user_name.select_one("span.c-usernameBlock__symbol")
tag_title: Optional[NavigableString] = ns if isinstance(
ns := next(tag_user_title.children, None), # type: ignore
NavigableString
) else None

status: str = tag_user_symbol.text.strip() if tag_user_symbol else ""
name: str = tag_user_name.text.strip().removeprefix(status).strip()
display_name: str = tag_user_display_name.text.strip()

title: str = ttd[0].strip() if len(ttd := tag_title_join_date.text.rsplit("|", 1)) > 1 else ""
join_date: datetime = parse_date(ttd[-1].strip().split(":", 1)[1])
title: str = tag_title.text.strip("| \n\r") if tag_title else ""
join_date: datetime = datetime.fromtimestamp(int(get_attr(tag_join_date, "data-time")))
avatar_url: str = "https:" + get_attr(tag_avatar, "src")
avatar_url = f"{avatar_url.rsplit('/', 1)[0]}/{quote(avatar_url.rsplit('/', 1)[1])}"
unique_id: UUID = UUID(get_attr(tag_unique_id, "href").split(":")[-1])

return {
"status": status,
Expand All @@ -658,6 +670,7 @@ def parse_user_header(user_header: Tag) -> dict[str, Any]:
"title": title,
"join_date": join_date,
"avatar_url": avatar_url,
"unique_id": unique_id,
}


Expand Down
4 changes: 4 additions & 0 deletions faapi/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime
from typing import Optional
from urllib.parse import quote
from uuid import UUID

from .connection import join_url
from .connection import root
Expand Down Expand Up @@ -184,6 +185,7 @@ def __init__(self, user_page: Optional[BeautifulSoup] = None):

super().__init__()

self.id: UUID = UUID(int=0)
self.user_page: Optional[BeautifulSoup] = user_page
self.title: str = ""
self.join_date: datetime = datetime.fromtimestamp(0)
Expand All @@ -201,6 +203,7 @@ def __init__(self, user_page: Optional[BeautifulSoup] = None):
self.parse()

def __iter__(self):
yield "id", self.id
yield "name", self.name
yield "display_name", self.display_name
yield "status", self.status
Expand Down Expand Up @@ -243,6 +246,7 @@ def parse(self, user_page: Optional[BeautifulSoup] = None):

parsed: dict = parse_user_page(self.user_page)

self.id = parsed["unique_id"]
self.name = parsed["name"]
self.display_name = parsed["display_name"]
self.status = parsed["status"]
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.12.7"
version = "3.13.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
2 changes: 1 addition & 1 deletion tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_make_session_error():
def test_get_robots(cookies: RequestsCookieJar):
result = get_robots(make_session(cookies, Session))
assert isinstance(result, RobotFileParser)
assert getattr(result, "default_entry", None) is not None
assert result.entries # type: ignore


def test_get(cookies: RequestsCookieJar):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_faapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def remove_user_icons(html: str) -> str:

def test_robots(cookies: RequestsCookieJar):
api: FAAPI = FAAPI(cookies)
assert getattr(api.robots, "default_entry") is not None
assert api.robots.entries # type: ignore
assert api.crawl_delay >= 1
assert api.check_path("/")
assert api.check_path("/view")
Expand Down
Loading