From b6dd88ff071faa51ba2a6e1e4de2f2513a750cee Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 16:35:01 +0200 Subject: [PATCH 1/6] implements a way to set api_key and user_id --- CHANGELOG.md | 10 +++++ Makefile | 4 +- docs/tutorials/downloader.py | 2 + rule34Py/rule34.py | 78 ++++++++++++++++++++++-------------- 4 files changed, 62 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3367386..ce27499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] - 2025-08-24 + +### Added + - Added a `rule34.autocomplete` method. + - Added `AutocompleteTag` class. + +### Changed + - Updated API wrapper to support website’s new authentication system. + - The underlying website API now **requires authentication** (`api_key` and `user_id`) for all requests. + ## [3.0.0] - 2025-06-09 ### Added diff --git a/Makefile b/Makefile index 8212798..d109c0b 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ $(wheels) &: $(dist_files) $(sdist) : $(dist_files) - $(POETRY) --output $(builddir) --format=sdist + $(POETRY) build --output $(builddir) --format=sdist # PHONY TARGETS # @@ -52,7 +52,7 @@ $(sdist) : $(dist_files) # Build all binary targets necessary for installation. # Does not build documentation or source distributions. -all : $(wheels) +all : $(wheels) $(sdist) .PHONY : all diff --git a/docs/tutorials/downloader.py b/docs/tutorials/downloader.py index cc97519..cbf4d1d 100644 --- a/docs/tutorials/downloader.py +++ b/docs/tutorials/downloader.py @@ -5,6 +5,8 @@ TAGS = ["neko", "sort:score", "-video"] results = client.search(tags=TAGS) +client.api_key = "YOUR_API_KEY" +client.user_id = "YOUR_USER_ID" from pathlib import Path import requests diff --git a/rule34Py/rule34.py b/rule34Py/rule34.py index c49f378..2a45659 100644 --- a/rule34Py/rule34.py +++ b/rule34Py/rule34.py @@ -52,7 +52,7 @@ SEARCH_RESULT_MAX: int = 1000 -class rule34Py(): +class rule34Py: """The rule34.xxx API client. This class is the primary broker for interactions with the real Rule34 servers. @@ -61,7 +61,7 @@ class rule34Py(): Example: .. code-block:: python - client = rule34Py() + client = rule34Py(api_key="API_KEY", user_id="USER_ID") post = client.get_post(1234) """ @@ -77,9 +77,23 @@ class rule34Py(): #: Defaults to either the value of the ``R34_USER_AGENT`` environment variable; or the ``rule34Py.rule34.DEFAULT_USER_AGENT``, if not asserted. #: Can be overridden by the user at runtime to change User-Agents. user_agent: str = os.environ.get("R34_USER_AGENT", DEFAULT_USER_AGENT) + #: User id required for requests by rule34.xxx + user_id: str = None + #: Api key required for requests by rule34.xxx + api_key: str = None def __init__(self): - """Initialize a new rule34 API client instance.""" + """Initialize a new rule34 API client instance. + + Args: + api_key: Api key from rule34.xxx + + user_id: User id from rule34.xxx account + + The api key and the user id can both be found at . + + """ + self.session = requests.session() self.session.mount(__base_url__, self._base_site_rate_limiter) @@ -99,20 +113,19 @@ def autocomplete(self, tag_string: str) -> list[AutocompleteTag]: """ # noqa: DOC502 params = [["q", tag_string]] formatted_url = self._parseUrlParams(API_URLS.AUTOCOMPLETE.value, params) - response = self._get(formatted_url, headers = { - "Referer": "https://rule34.xxx/", - "Origin": "https://rule34.xxx", - "Accept": "*/*" - }) + response = self._get( + formatted_url, + headers={ + "Referer": "https://rule34.xxx/", + "Origin": "https://rule34.xxx", + "Accept": "*/*", + }, + ) response.raise_for_status() raw_data = response.json() results = [ - AutocompleteTag( - label=item["label"], - value=item["value"], - type=item["type"] - ) + AutocompleteTag(label=item["label"], value=item["value"], type=item["type"]) for item in raw_data ] return results @@ -124,11 +137,21 @@ def _get(self, *args, **kwargs) -> requests.Response: Returns: The Response object from the GET request. + + Raises: + ValueError: API credentials aer not supplied. """ + # check if api credentials are set + if self.user_id == None and self.api_key == None: + raise ValueError("API credentials must be supplied, api_key and user_id can not be None!\nSee https://api.rule34.xxx/ for more information.") + # headers kwargs.setdefault("headers", {}) kwargs["headers"].setdefault("User-Agent", self.user_agent) + # api authentication + kwargs["params"] = {"api_key": self.api_key, "user_id": self.user_id} + # cookies kwargs.setdefault("cookies", {}) if self.captcha_clearance is not None: @@ -152,9 +175,7 @@ def get_comments(self, post_id: int) -> list[PostComment]: Raises: requests.HTTPError: The backing HTTP GET operation failed. """ # noqa: DOC502 - params = [ - ["POST_ID", str(post_id)] - ] + params = [["POST_ID", str(post_id)]] formatted_url = self._parseUrlParams(API_URLS.COMMENTS, params) response = self._get(formatted_url) response.raise_for_status() @@ -163,11 +184,11 @@ def get_comments(self, post_id: int) -> list[PostComment]: comment_soup = BeautifulSoup(response.content.decode("utf-8"), features="xml") for e_comment in comment_soup.find_all("comment"): comment = PostComment( - id = e_comment["id"], - owner_id = e_comment["creator_id"], - body = e_comment["body"], - post_id = e_comment["post_id"], - creation = e_comment["created_at"], + id=e_comment["id"], + owner_id=e_comment["creator_id"], + body=e_comment["body"], + post_id=e_comment["post_id"], + creation=e_comment["created_at"], ) comments.append(comment) @@ -188,9 +209,7 @@ def get_pool(self, pool_id: int) -> Pool: Raises: requests.HTTPError: The backing HTTP GET operation failed. """ # noqa: DOC502 - params = [ - ["POOL_ID", str(pool_id)] - ] + params = [["POOL_ID", str(pool_id)]] response = self._get(self._parseUrlParams(API_URLS.POOL.value, params)) response.raise_for_status() return PoolPage.pool_from_html(response.text) @@ -207,9 +226,7 @@ def get_post(self, post_id: int) -> Union[Post, None]: Raises: requests.HTTPError: The backing HTTP GET operation failed. """ # noqa: DOC502 - params = [ - ["POST_ID", str(post_id)] - ] + params = [["POST_ID", str(post_id)]] formatted_url = self._parseUrlParams(API_URLS.GET_POST.value, params) response = self._get(formatted_url) response.raise_for_status() @@ -311,7 +328,7 @@ def random_post(self) -> Post: Returns: A random Post. - + Raises: requests.HTTPError: The backing HTTP GET operation failed. """ # noqa: DOC502 @@ -335,9 +352,10 @@ def random_post_id(self) -> int: response = self._get(API_URLS.RANDOM_POST.value) response.raise_for_status() parsed = urlparse.urlparse(response.url) - return int(parse_qs(parsed.query)['id'][0]) + return int(parse_qs(parsed.query)["id"][0]) - def search(self, + def search( + self, tags: list[str] = [], page_id: Union[int, None] = None, limit: int = SEARCH_RESULT_MAX, From 2ac5412ba317b0b8e30d1be38e2a4fd0ba420ddf Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 17:29:21 +0200 Subject: [PATCH 2/6] added guide to receiving and setting api credentials --- docs/_static/api-credentials.png | Bin 0 -> 31459 bytes docs/guides/api-credentials.rst | 44 +++++++++++++++++++++++++++++++ docs/guides/index.rst | 1 + 3 files changed, 45 insertions(+) create mode 100644 docs/_static/api-credentials.png create mode 100644 docs/guides/api-credentials.rst diff --git a/docs/_static/api-credentials.png b/docs/_static/api-credentials.png new file mode 100644 index 0000000000000000000000000000000000000000..d72748a1bbaca5d97431301492ca4ea30d4cf96e GIT binary patch literal 31459 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
  • 4) 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 literal 0 HcmV?d00001 diff --git a/docs/guides/api-credentials.rst b/docs/guides/api-credentials.rst new file mode 100644 index 0000000..29e19f4 --- /dev/null +++ b/docs/guides/api-credentials.rst @@ -0,0 +1,44 @@ +================================= +How to set Rule34 api credentials +================================= + +Since Aug 19, 2025 the `api.rule34.xxx `_ REST API requires credentials for all requests made with the REST API. With that changes also came a rate limit of 60 request per 60 seconds. + +In order to be able to make requests with `rule34Py `_, you now have to set the api key and your user id. + +Reference the :doc:`rule34Py.rule34Py <../../api/rule34Py/rule34>` class documentation for an indication of which workflows have these limits. + +Note that as of now, you need an `rule34.xxx `_ account. + + +Setting api credentials +======================= + +A `rule34.xxx `_ account is required to receive REST API credentials. + +#. Use any reasonable browser with javascript enabled to open any URL to the https://rule34.xxx site. + +#. Login without account + +#. Navigate to https://rule34.xxx/index.php?page=account&s=options + +#. Scroll down to **API Access Credentials**, there you will find a long string similar to the one bellow. If you don't see a text similar to the one below, click the checkbox *Generate New Key?* and then *Save* at the bottom, revisit the site. + + .. image:: /_static/api-credentials.png + + - The `api_key` is highlighted in yellow (the long block of text with 128 letters and numbers) + - The `user_id` is highlighted in green (the short block of seven digits) + + .. important:: + + Do not share those information with anyone online! + +#. Set the in the previous step retrieved credentials like the following. + + .. code-block:: python + + import rule34Py as r34 + client = r34.rule34Py() + + client.api_key="00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + client.user_id="0000000" diff --git a/docs/guides/index.rst b/docs/guides/index.rst index c4f4c8d..37a8b1e 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -7,4 +7,5 @@ This section contains how-to guides and special topics of interest to module use :maxdepth: 1 captcha-clearance + api-credentials package-metadata From 073ac6f5203894879cc6c39306c265054233c9cc Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 20:16:51 +0200 Subject: [PATCH 3/6] updated unit tests and added check for url api parameters Took me waaaay to long to get the tests working again. --- rule34Py/rule34.py | 7 ++++-- tests/fixtures/mock34/responses.yml | 34 ++++++++++++++--------------- tests/unit/conftest.py | 2 ++ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/rule34Py/rule34.py b/rule34Py/rule34.py index 2a45659..bb2c3ff 100644 --- a/rule34Py/rule34.py +++ b/rule34Py/rule34.py @@ -141,8 +141,10 @@ def _get(self, *args, **kwargs) -> requests.Response: Raises: ValueError: API credentials aer not supplied. """ + is_api_request = args[0].startswith(__api_url__) == True + # check if api credentials are set - if self.user_id == None and self.api_key == None: + if is_api_request and self.user_id == None and self.api_key == None: raise ValueError("API credentials must be supplied, api_key and user_id can not be None!\nSee https://api.rule34.xxx/ for more information.") # headers @@ -150,7 +152,8 @@ def _get(self, *args, **kwargs) -> requests.Response: kwargs["headers"].setdefault("User-Agent", self.user_agent) # api authentication - kwargs["params"] = {"api_key": self.api_key, "user_id": self.user_id} + if is_api_request: + kwargs["params"] = {"api_key": self.api_key, "user_id": self.user_id} # cookies kwargs.setdefault("cookies", {}) diff --git a/tests/fixtures/mock34/responses.yml b/tests/fixtures/mock34/responses.yml index d52c76f..d32aa2e 100644 --- a/tests/fixtures/mock34/responses.yml +++ b/tests/fixtures/mock34/responses.yml @@ -33468,7 +33468,7 @@ responses: vary: Accept-Encoding method: GET status: 200 - url: https://api.rule34.xxx/index.php?page=dapi&s=comment&q=index&post_id=4153825 + url: https://api.rule34.xxx/index.php?page=dapi&s=comment&q=index&post_id=4153825&api_key=0000000&user_id=0000000 - response: auto_calculate_content_length: false body: '[{"preview_url":"https:\/\/api-cdn.rule34.xxx\/thumbnails\/3672\/thumbnail_629eb1432db601f8555a02d3e22b5fa7.jpg","sample_url":"https:\/\/api-cdn.rule34.xxx\/images\/3672\/629eb1432db601f8555a02d3e22b5fa7.jpeg","file_url":"https:\/\/api-cdn.rule34.xxx\/images\/3672\/629eb1432db601f8555a02d3e22b5fa7.jpeg","directory":3672,"hash":"629eb1432db601f8555a02d3e22b5fa7","width":811,"height":811,"id":4153825,"image":"629eb1432db601f8555a02d3e22b5fa7.jpeg","change":1710722744,"owner":"chocola-chan","parent_id":3984379,"rating":"explicit","sample":false,"sample_height":0,"sample_width":0,"score":118,"tags":"1girls @@ -33484,7 +33484,7 @@ responses: alt-svc: h3=":443"; ma=86400 method: GET status: 200 - url: https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&id=4153825&json=1 + url: https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&id=4153825&json=1&api_key=0000000&user_id=0000000 - response: auto_calculate_content_length: false body: "\n\n\t\n\t\t\n\t\t\n\n\t\n\t\t\n\t\t\n\n\n
    \n \ @@ -103503,7 +103503,7 @@ responses: vary: Accept-Encoding method: GET status: 200 - url: https://api.rule34.xxx/index.php?page=dapi&s=comment&q=index&post_id=4153825 + url: https://api.rule34.xxx/index.php?page=dapi&s=comment&q=index&post_id=4153825&api_key=0000000&user_id=0000000 - response: auto_calculate_content_length: false body: \n\n\n Example Domain\n\n\ diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 1a8dcba..d5e2cf8 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -7,4 +7,6 @@ @pytest.fixture(scope="module") def rule34(mock34): r34 = rule34Py() + r34.api_key = "0000000" + r34.user_id = "0000000" yield r34 From 04a2b686de19b17462562bb679ef90f3bdff1405 Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 20:22:52 +0200 Subject: [PATCH 4/6] fixed example in README.md and fixed download.py tutorial --- README.md | 18 ++++++++++-------- docs/tutorials/downloader.py | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0d41f1b..7bf6c13 100644 --- a/README.md +++ b/README.md @@ -24,28 +24,30 @@ See the [Developer Guide](https://b3yc0d3.github.io/rule34Py/dev/developer-guide ```python from rule34Py import rule34Py -r34Py = rule34Py() +client = rule34Py() +client.api_key = "YOUR_API_KEY" +client.user_id = "YOUR_USER_ID" # Get comments of an post. -r34Py.get_comments(4153825) +client.get_comments(4153825) # Get post by its id. -r34Py.get_post(4153825) +client.get_post(4153825) # Get top 100 icame. -r34Py.icame() +client.icame() # Search for posts by tag(s). -r34Py.search(["neko"], page_id=2, limit=50) +client.search(["neko"], page_id=2, limit=50) # Get pool by id. -r34Py.get_pool(28) +client.get_pool(28) # Get a random post. -random = r34Py.random_post() +random = client.random_post() # Get just a random post ID. -random_id = r34Py.random_post_id() +random_id = client.random_post_id() ``` diff --git a/docs/tutorials/downloader.py b/docs/tutorials/downloader.py index cbf4d1d..044be4e 100644 --- a/docs/tutorials/downloader.py +++ b/docs/tutorials/downloader.py @@ -1,12 +1,12 @@ from rule34Py import rule34Py client = rule34Py() +client.api_key = "YOUR_API_KEY" +client.user_id = "YOUR_USER_ID" TAGS = ["neko", "sort:score", "-video"] results = client.search(tags=TAGS) -client.api_key = "YOUR_API_KEY" -client.user_id = "YOUR_USER_ID" from pathlib import Path import requests From bfb38b7811766b06bd6d2f1010d894e85124287c Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 20:51:38 +0200 Subject: [PATCH 5/6] fixed code example in rst docstring of rule34Py class and exclude private methods from sphinx --- docs/conf.py | 1 + rule34Py/rule34.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index eb5020e..cf85cf7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,6 +45,7 @@ # sphinx.ext.autodoc configuration # autodoc_default_options = { + "private-members": False } autodoc_typehints = "both" # Show typehints in the signature and as content of the function or method diff --git a/rule34Py/rule34.py b/rule34Py/rule34.py index bb2c3ff..491d841 100644 --- a/rule34Py/rule34.py +++ b/rule34Py/rule34.py @@ -61,7 +61,10 @@ class rule34Py: Example: .. code-block:: python - client = rule34Py(api_key="API_KEY", user_id="USER_ID") + client = rule34Py() + client.api_key="API_KEY" + client.user_id="USER_ID" + post = client.get_post(1234) """ @@ -77,9 +80,9 @@ class rule34Py: #: Defaults to either the value of the ``R34_USER_AGENT`` environment variable; or the ``rule34Py.rule34.DEFAULT_USER_AGENT``, if not asserted. #: Can be overridden by the user at runtime to change User-Agents. user_agent: str = os.environ.get("R34_USER_AGENT", DEFAULT_USER_AGENT) - #: User id required for requests by rule34.xxx + #: User id required for requests by `rule34.xxx `_ user_id: str = None - #: Api key required for requests by rule34.xxx + #: Api key required for requests by `rule34.xxx `_ api_key: str = None def __init__(self): From 11dbbcfae315cca025926c6a614dd59ccf3155ce Mon Sep 17 00:00:00 2001 From: b3yc0d3 Date: Sat, 30 Aug 2025 21:03:19 +0200 Subject: [PATCH 6/6] fixed blank line after docstring --- rule34Py/rule34.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rule34Py/rule34.py b/rule34Py/rule34.py index 491d841..7cdbe69 100644 --- a/rule34Py/rule34.py +++ b/rule34Py/rule34.py @@ -94,9 +94,7 @@ def __init__(self): user_id: User id from rule34.xxx account The api key and the user id can both be found at . - """ - self.session = requests.session() self.session.mount(__base_url__, self._base_site_rate_limiter)