simple-rule34 0.1.6.1__tar.gz → 1.0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. simple_rule34-1.0.0.1/PKG-INFO +20 -0
  2. simple_rule34-1.0.0.1/pyproject.toml +27 -0
  3. simple_rule34-1.0.0.1/src/SimpleRule34/__init__.py +1 -0
  4. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/src/SimpleRule34/exceptions.py +0 -8
  5. simple_rule34-1.0.0.1/src/SimpleRule34/main.py +207 -0
  6. simple_rule34-1.0.0.1/src/SimpleRule34/types.py +133 -0
  7. simple_rule34-1.0.0.1/src/SimpleRule34/utils.py +31 -0
  8. simple_rule34-1.0.0.1/src/simple_rule34.egg-info/PKG-INFO +20 -0
  9. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/src/simple_rule34.egg-info/SOURCES.txt +1 -1
  10. simple_rule34-1.0.0.1/src/simple_rule34.egg-info/requires.txt +3 -0
  11. simple_rule34-1.0.0.1/tests/test.py +17 -0
  12. simple_rule34-0.1.6.1/PKG-INFO +0 -58
  13. simple_rule34-0.1.6.1/pyproject.toml +0 -65
  14. simple_rule34-0.1.6.1/src/SimpleRule34/Rule34.py +0 -151
  15. simple_rule34-0.1.6.1/src/SimpleRule34/__init__.py +0 -1
  16. simple_rule34-0.1.6.1/src/SimpleRule34/types.py +0 -62
  17. simple_rule34-0.1.6.1/src/SimpleRule34/utils.py +0 -22
  18. simple_rule34-0.1.6.1/src/simple_rule34.egg-info/PKG-INFO +0 -58
  19. simple_rule34-0.1.6.1/src/simple_rule34.egg-info/requires.txt +0 -41
  20. simple_rule34-0.1.6.1/tests/test.py +0 -40
  21. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/LICENSE +0 -0
  22. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/README.md +0 -0
  23. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/setup.cfg +0 -0
  24. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/src/__init__.py +0 -0
  25. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/src/simple_rule34.egg-info/dependency_links.txt +0 -0
  26. {simple_rule34-0.1.6.1 → simple_rule34-1.0.0.1}/src/simple_rule34.egg-info/top_level.txt +0 -0
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple_rule34
3
+ Version: 1.0.0.1
4
+ Summary: Simple api wrapper of rule34.xxx for python with asynchronous support
5
+ Author-email: StarMan12 <author@example.com>
6
+ Project-URL: Homepage, https://github.com/SyperAlexKomp/simple-rule34-api
7
+ Project-URL: Bug Tracker, https://github.com/SyperAlexKomp/simple-rule34-api/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: aiofiles~=25.1.0
15
+ Requires-Dist: aiohttp~=3.12.15
16
+ Requires-Dist: pydantic~=2.12.5
17
+ Dynamic: license-file
18
+
19
+ # rule34-simple-api
20
+ Simple api wrapper of rule34.xxx for python with asynchronous support
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "simple_rule34"
7
+ version = "1.0.0.1"
8
+ authors = [
9
+ { name="StarMan12", email="author@example.com" },
10
+ ]
11
+ description = "Simple api wrapper of rule34.xxx for python with asynchronous support"
12
+ readme = "README.md"
13
+ requires-python = ">=3.9"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ 'aiofiles~=25.1.0',
21
+ 'aiohttp~=3.12.15',
22
+ 'pydantic~=2.12.5'
23
+ ]
24
+
25
+ [project.urls]
26
+ "Homepage" = "https://github.com/SyperAlexKomp/simple-rule34-api"
27
+ "Bug Tracker" = "https://github.com/SyperAlexKomp/simple-rule34-api/issues"
@@ -0,0 +1 @@
1
+ from .main import Rule34Api
@@ -1,11 +1,3 @@
1
- class ToBigRequestException(Exception):
2
- def __init__(self, msg):
3
- self.msg = msg
4
-
5
- def __str__(self):
6
- return self.msg
7
-
8
-
9
1
  class RequestMoreThanAvailableException(Exception):
10
2
  def __init__(self, msg, r_c, a_c):
11
3
  self.msg = msg
@@ -0,0 +1,207 @@
1
+ import json
2
+ import xml.etree.ElementTree as ET
3
+
4
+ from .types import *
5
+
6
+
7
+ class Rule34BaseApi:
8
+ _header = {'User-Agent': 'rule34-simple-api (Request)'}
9
+ _url = f"https://api.rule34.xxx/index.php"
10
+
11
+ def __init__(self, user_id: int, api_key: str, **kwargs):
12
+ self._params = {
13
+ 'user_id': user_id,
14
+ 'api_key': api_key,
15
+ 'page': "dapi",
16
+ 'q': "index",
17
+ **kwargs,
18
+ }
19
+
20
+ async def _get(self, json_: bool = True, **params) -> dict | str:
21
+ """
22
+ Raw request method
23
+
24
+ :return:
25
+ """
26
+
27
+ # API allow boolean values only in 0/1 format, so we need to convert default boolean to this format
28
+ json_parsed = 1 if json_ else 0
29
+
30
+ async with aiohttp.ClientSession(headers=self._header) as session:
31
+ async with session.get(self._url, params={**self._params, **params, 'json': json_parsed}) as response:
32
+ if not response.ok:
33
+ raise ApiException(await response.text())
34
+
35
+ # Used for handling XML response
36
+ if not json_:
37
+ return await response.text()
38
+
39
+ try:
40
+ return await response.json()
41
+ except json.decoder.JSONDecodeError:
42
+ raise ApiException(await response.text())
43
+
44
+
45
+ class Rule34PostApi(Rule34BaseApi):
46
+ def __init__(self, user_id: int, api_key: str, **kwargs):
47
+ super().__init__(user_id, api_key, **kwargs)
48
+ self._params['s'] = "post"
49
+
50
+ async def get(self, id: int) -> Rule34Post:
51
+ """
52
+ Method used to obtain a post by its ID
53
+
54
+ :param id: Post ID
55
+ :return: Post
56
+ """
57
+
58
+ post = await self._get(id=id)
59
+ return Rule34Post(**post[0])
60
+
61
+ async def get_count(self, tags: list[str] = None) -> int:
62
+ """
63
+ Method used to get amount of all posts based on given tags.
64
+ This value also includes deleted posts, actual amount of posts may be less.
65
+
66
+ :param tags: List of tags
67
+ :return: Amount of posts
68
+ """
69
+
70
+ if tags is None:
71
+ tags = []
72
+
73
+ xml_data = await self._get(json_=False, tags=" ".join(tags))
74
+ xml_root = ET.fromstring(xml_data)
75
+
76
+ return int(xml_root.get('count'))
77
+
78
+ async def get_list(self, amount: int = 1000, page: int = 0, tags: list[str] = None,
79
+ forbidden_tags: list[str] = None) -> list[Rule34Post]:
80
+ """
81
+ Method used to obtain list of posts based on given tags.
82
+
83
+ :param amount: Amount of posts that will be searched with this request
84
+ :param page: Page number
85
+ :param tags: List of tags
86
+ :param forbidden_tags: List of tags posts with whom will be removed from list. When you specify forbidden tags returned amount of posts may be less than specified in amount value.
87
+ :return: List of posts
88
+ """
89
+
90
+ if tags is None:
91
+ tags = []
92
+ if forbidden_tags is None:
93
+ forbidden_tags = []
94
+
95
+ if amount > 1000:
96
+ raise ValueError(f"The max size of request is 1000 when you tried to request {amount}")
97
+
98
+ raw_list = await self._get(limit=amount, pid=page, tags=" ".join(tags))
99
+ post_list = [Rule34Post(**data) for data in raw_list]
100
+
101
+ # Fast return if not sort is needed
102
+ if len(forbidden_tags) < 1:
103
+ return post_list
104
+
105
+ sorted_post_list = []
106
+ for post in post_list:
107
+ if any(tag in forbidden_tags for tag in post.tags):
108
+ pass
109
+ else:
110
+ sorted_post_list.append(post)
111
+
112
+ return sorted_post_list
113
+
114
+
115
+ class Rule34CommentsApi(Rule34BaseApi):
116
+ def __init__(self, user_id: int, api_key: str, **kwargs):
117
+ super().__init__(user_id, api_key, **kwargs)
118
+ self._params['s'] = "comment"
119
+
120
+ async def get(self, post_id: int) -> list[Rule34Comment]:
121
+ """
122
+ Method used to obtain list of comments based on given post id.
123
+
124
+ :param post_id: Post ID
125
+ :return: List of comments
126
+ """
127
+
128
+ xml_data = await self._get(post_id=post_id, json_=False)
129
+ xml_root = ET.fromstring(xml_data)
130
+
131
+ return [Rule34Comment(**comment_e.attrib) for comment_e in xml_root.findall("comment")]
132
+
133
+
134
+ class Rule34TagsApi(Rule34BaseApi):
135
+ def __init__(self, user_id: int, api_key: str, **kwargs):
136
+ super().__init__(user_id, api_key, **kwargs)
137
+ self._params['s'] = "tag"
138
+
139
+ async def get(self, id: int) -> Rule34Tag | None:
140
+ """
141
+ Method used to obtain a tag data by its ID
142
+
143
+ :param id: Tag ID
144
+ :return: Tag
145
+ """
146
+
147
+ xml_data = await self._get(id=id, json_=False)
148
+ xml_root = ET.fromstring(xml_data)
149
+
150
+ raw_tag_data = xml_root.find("tag")
151
+ if raw_tag_data is None:
152
+ return None
153
+
154
+ return Rule34Tag(**raw_tag_data.attrib)
155
+
156
+ async def get_list(self, amount: int = 100, page: int = 0) -> list[Rule34Tag]:
157
+ """
158
+ Method used to obtain given amount of tags.
159
+
160
+ :param amount: Amount of tags you want to obtain
161
+ :return: List of tags
162
+ """
163
+
164
+ xml_data = await self._get(json_=False, limit=amount, pid=page)
165
+ xml_root = ET.fromstring(xml_data)
166
+
167
+ return [Rule34Tag(**tag_e.attrib) for tag_e in xml_root.findall("tag")]
168
+
169
+
170
+ class Rule34AutocompleteApi(Rule34BaseApi):
171
+ def __init__(self, user_id: int, api_key: str, **kwargs):
172
+ super().__init__(user_id, api_key, **kwargs)
173
+ self._url = "https://api.rule34.xxx/autocomplete.php"
174
+ self._params = {}
175
+
176
+ async def search(self, text: str) -> list[Rule34Autocomplete]:
177
+ return [Rule34Autocomplete(**data) for data in eval(await self._get(json_=False, q=text))]
178
+
179
+
180
+ class Rule34Api:
181
+ def __init__(self, user_id: int, api_key: str, **kwargs):
182
+ """
183
+ After 13.07.2025 you need to use API key and user id to access the API.
184
+ More info: https://discord.com/channels/336564284207267850/497927834241859586/1393885318477906021
185
+
186
+ :param user_id: User ID
187
+ :param api_key: API Key
188
+ :param kwargs: Any other parameters that you want to pass to ANY requests. (No use cases, but may be used if any other global params will be added in the future)
189
+ """
190
+ self._user_id = user_id
191
+ self._api_key = api_key
192
+
193
+ @property
194
+ def post(self) -> Rule34PostApi:
195
+ return Rule34PostApi(user_id=self._user_id, api_key=self._api_key)
196
+
197
+ @property
198
+ def comments(self) -> Rule34CommentsApi:
199
+ return Rule34CommentsApi(user_id=self._user_id, api_key=self._api_key)
200
+
201
+ @property
202
+ def tags(self) -> Rule34TagsApi:
203
+ return Rule34TagsApi(user_id=self._user_id, api_key=self._api_key)
204
+
205
+ @property
206
+ def autocomplete(self) -> Rule34AutocompleteApi:
207
+ return Rule34AutocompleteApi(user_id=self._user_id, api_key=self._api_key)
@@ -0,0 +1,133 @@
1
+ import os
2
+ import typing
3
+ import re
4
+ import warnings
5
+
6
+ from enum import Enum
7
+ from pathlib import Path
8
+
9
+ import aiofiles
10
+ import aiohttp
11
+
12
+ from pydantic import BaseModel, field_validator, HttpUrl, Field
13
+
14
+ from .exceptions import ApiException
15
+ from .utils import get_file_type
16
+
17
+
18
+ class File(BaseModel):
19
+ url: HttpUrl
20
+ type: str = None
21
+
22
+ def __init__(self, /, **data: typing.Any) -> None:
23
+ super().__init__(**data)
24
+
25
+ self.type = get_file_type(str(self.url))
26
+
27
+ async def download(self, path: Path | str = "./rule34_downloads", file_name: Path | str = None) -> Path:
28
+ if isinstance(path, str):
29
+ path = Path(path)
30
+ if isinstance(file_name, str):
31
+ file_name = Path(file_name)
32
+
33
+ # Create storage path
34
+ path.mkdir(parents=True, exist_ok=True)
35
+
36
+ async with aiohttp.ClientSession() as session:
37
+ async with session.get(str(self.url)) as response:
38
+ if response.status != 200:
39
+ raise ApiException(f"Api returned status code {response.status} with message"
40
+ f" {await response.text()}")
41
+
42
+ original_file_name = Path(os.path.basename(str(self.url)))
43
+ file_name = original_file_name if file_name is None else file_name
44
+ if file_name.suffix != original_file_name.suffix:
45
+ warnings.warn("Provided file name suffix does not match original file name suffix. File can be corrupted.")
46
+
47
+ save_path = path / file_name
48
+
49
+ async with aiofiles.open(save_path, 'wb') as file:
50
+ await file.write(await response.read())
51
+
52
+ return save_path
53
+
54
+
55
+ class Rule34Post(BaseModel):
56
+ id: int
57
+ owner: str | None
58
+ status: str | None
59
+ rating: str | None
60
+ score: int | None
61
+
62
+ preview_file: File = Field(validation_alias="preview_url")
63
+ sample_file: File = Field(validation_alias="sample_url")
64
+ file: File = Field(validation_alias="file_url")
65
+ source: str | None
66
+
67
+ width: int | None
68
+ height: int | None
69
+ hash: str | None
70
+ image: str | None
71
+ directory: int | None
72
+
73
+ change: int | None
74
+ parent_id: int | None
75
+ has_notes: bool | None
76
+ comment_count: int | None
77
+
78
+ sample: bool | None
79
+ sample_height: int | None
80
+ sample_width: int | None
81
+
82
+ tags: typing.List[str] | None
83
+
84
+ @field_validator('tags', mode='before')
85
+ @classmethod
86
+ def split_tags(cls, v):
87
+ if isinstance(v, str):
88
+ return v.strip().split()
89
+ return v
90
+
91
+ @field_validator('preview_file', 'sample_file', 'file', mode='before', check_fields=False)
92
+ @classmethod
93
+ def wrap_in_file(cls, v):
94
+ if isinstance(v, (str, HttpUrl)):
95
+ return File(url=v)
96
+ return v
97
+
98
+ class Config:
99
+ populate_by_name = True
100
+
101
+
102
+ class Rule34Comment(BaseModel):
103
+ id: int
104
+ post_id: int
105
+ message: str = Field(alias="body")
106
+ creator: str
107
+ creator_id: int
108
+
109
+
110
+ class Rule34TagType(Enum):
111
+ GENERAL = "0"
112
+ ARTIST = "1"
113
+ COPYRIGHT = "3"
114
+ UNKNOWN = "2"
115
+ CHARACTER = "4"
116
+ META = "5"
117
+
118
+
119
+ class Rule34Tag(BaseModel):
120
+ id: int
121
+ type: Rule34TagType
122
+ name: str
123
+ count: int = Field(default_factory=int)
124
+ ambiguous: bool
125
+
126
+
127
+ class Rule34Autocomplete(BaseModel):
128
+ label: str
129
+ value: str
130
+
131
+ @property
132
+ def count(self) -> int:
133
+ return int(re.search(r'\((\d+)\)', self.label).group(1))
@@ -0,0 +1,31 @@
1
+ FILE_TYPES = {
2
+ 'jpg': "photo",
3
+ 'jpeg': "photo",
4
+ 'png': "photo",
5
+
6
+ 'mp4': "video",
7
+ 'avi': "video",
8
+ 'mov': "video",
9
+ 'webm': "video",
10
+
11
+ 'gif': "animation",
12
+ }
13
+
14
+
15
+ async def get_file_size(url, session):
16
+ async with session.head(url=url, allow_redirects=True) as response:
17
+ if 'Content-Length' in response.headers:
18
+ size = int(response.headers['Content-Length'])
19
+ return size
20
+ else:
21
+ return None
22
+
23
+
24
+ def get_file_type(url) -> str | None:
25
+ file_extension = url.split('.')[-1].lower()
26
+
27
+ if file_extension not in FILE_TYPES.keys(): return None
28
+
29
+ return FILE_TYPES[file_extension]
30
+
31
+
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple_rule34
3
+ Version: 1.0.0.1
4
+ Summary: Simple api wrapper of rule34.xxx for python with asynchronous support
5
+ Author-email: StarMan12 <author@example.com>
6
+ Project-URL: Homepage, https://github.com/SyperAlexKomp/simple-rule34-api
7
+ Project-URL: Bug Tracker, https://github.com/SyperAlexKomp/simple-rule34-api/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: aiofiles~=25.1.0
15
+ Requires-Dist: aiohttp~=3.12.15
16
+ Requires-Dist: pydantic~=2.12.5
17
+ Dynamic: license-file
18
+
19
+ # rule34-simple-api
20
+ Simple api wrapper of rule34.xxx for python with asynchronous support
@@ -2,9 +2,9 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/__init__.py
5
- src/SimpleRule34/Rule34.py
6
5
  src/SimpleRule34/__init__.py
7
6
  src/SimpleRule34/exceptions.py
7
+ src/SimpleRule34/main.py
8
8
  src/SimpleRule34/types.py
9
9
  src/SimpleRule34/utils.py
10
10
  src/simple_rule34.egg-info/PKG-INFO
@@ -0,0 +1,3 @@
1
+ aiofiles~=25.1.0
2
+ aiohttp~=3.12.15
3
+ pydantic~=2.12.5
@@ -0,0 +1,17 @@
1
+ import asyncio
2
+
3
+ from src.SimpleRule34 import Rule34Api
4
+
5
+ api = Rule34Api(api_key="3cb81d49b14a1f834f442c020d429bef836c3c2c29b8cc345f961d4c85d72daa07d970cd422059c2333d229da0efc38179057373f1da8d267126d542acdf74d6", user_id=5260076)
6
+
7
+ async def foo(_id):
8
+ p = await api.get_post_list(tags=["genshin_impact", f"id:<={_id}"], page=27, limit=1000)
9
+ return p[-1].id
10
+
11
+ async def main():
12
+ posts = await api.
13
+
14
+
15
+
16
+ if __name__ == '__main__':
17
+ asyncio.run(main())
@@ -1,58 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: simple_rule34
3
- Version: 0.1.6.1
4
- Summary: Simple api wrapper of rule34.xxx for python with asynchronous support
5
- Author-email: StarMan12 <author@example.com>
6
- Project-URL: Homepage, https://github.com/SyperAlexKomp/simple-rule34-api
7
- Project-URL: Bug Tracker, https://github.com/SyperAlexKomp/simple-rule34-api/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.9
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: aiohttp==3.8.4
15
- Requires-Dist: aiosignal==1.3.1
16
- Requires-Dist: alabaster==0.7.13
17
- Requires-Dist: async-timeout==4.0.2
18
- Requires-Dist: attrs==23.1.0
19
- Requires-Dist: Babel==2.9.1
20
- Requires-Dist: build==0.10.0
21
- Requires-Dist: certifi==2023.5.7
22
- Requires-Dist: charset-normalizer==3.2.0
23
- Requires-Dist: colorama==0.4.6
24
- Requires-Dist: docopt==0.6.2
25
- Requires-Dist: docutils==0.18.1
26
- Requires-Dist: frozenlist==1.4.0
27
- Requires-Dist: idna==3.4
28
- Requires-Dist: imagesize==1.4.1
29
- Requires-Dist: importlib-metadata==6.8.0
30
- Requires-Dist: Jinja2==3.1.2
31
- Requires-Dist: MarkupSafe==2.1.3
32
- Requires-Dist: multidict==6.0.4
33
- Requires-Dist: packaging==23.1
34
- Requires-Dist: pipreqs==0.4.13
35
- Requires-Dist: Pygments==2.15.1
36
- Requires-Dist: pyproject_hooks==1.0.0
37
- Requires-Dist: requests==2.31.0
38
- Requires-Dist: snowballstemmer==2.2.0
39
- Requires-Dist: Sphinx==6.2.1
40
- Requires-Dist: sphinx-rtd-theme==1.2.2
41
- Requires-Dist: sphinxcontrib-applehelp==1.0.4
42
- Requires-Dist: sphinxcontrib-devhelp==1.0.2
43
- Requires-Dist: sphinxcontrib-htmlhelp==2.0.1
44
- Requires-Dist: sphinxcontrib-jquery==4.1
45
- Requires-Dist: sphinxcontrib-jsmath==1.0.1
46
- Requires-Dist: sphinxcontrib-qthelp==1.0.3
47
- Requires-Dist: sphinxcontrib-serializinghtml==1.1.5
48
- Requires-Dist: tomli==2.0.1
49
- Requires-Dist: urllib3==2.0.3
50
- Requires-Dist: yarg==0.1.9
51
- Requires-Dist: yarl==1.9.2
52
- Requires-Dist: zipp==3.16.2
53
- Requires-Dist: aiofiles~=23.2.1
54
- Requires-Dist: pydantic~=2.7.1
55
- Dynamic: license-file
56
-
57
- # rule34-simple-api
58
- Simple api wrapper of rule34.xxx for python with asynchronous support
@@ -1,65 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "simple_rule34"
7
- version = "0.1.6.1"
8
- authors = [
9
- { name="StarMan12", email="author@example.com" },
10
- ]
11
- description = "Simple api wrapper of rule34.xxx for python with asynchronous support"
12
- readme = "README.md"
13
- requires-python = ">=3.9"
14
- classifiers = [
15
- "Programming Language :: Python :: 3",
16
- "License :: OSI Approved :: MIT License",
17
- "Operating System :: OS Independent",
18
- ]
19
- dependencies = [
20
- 'aiohttp==3.8.4',
21
- 'aiosignal==1.3.1',
22
- 'alabaster==0.7.13',
23
- 'async-timeout==4.0.2',
24
- 'attrs==23.1.0',
25
- 'Babel==2.9.1',
26
- 'build==0.10.0',
27
- 'certifi==2023.5.7',
28
- 'charset-normalizer==3.2.0',
29
- 'colorama==0.4.6',
30
- 'docopt==0.6.2',
31
- 'docutils==0.18.1',
32
- 'frozenlist==1.4.0',
33
- 'idna==3.4',
34
- 'imagesize==1.4.1',
35
- 'importlib-metadata==6.8.0',
36
- 'Jinja2==3.1.2',
37
- 'MarkupSafe==2.1.3',
38
- 'multidict==6.0.4',
39
- 'packaging==23.1',
40
- 'pipreqs==0.4.13',
41
- 'Pygments==2.15.1',
42
- 'pyproject_hooks==1.0.0',
43
- 'requests==2.31.0',
44
- 'snowballstemmer==2.2.0',
45
- 'Sphinx==6.2.1',
46
- 'sphinx-rtd-theme==1.2.2',
47
- 'sphinxcontrib-applehelp==1.0.4',
48
- 'sphinxcontrib-devhelp==1.0.2',
49
- 'sphinxcontrib-htmlhelp==2.0.1',
50
- 'sphinxcontrib-jquery==4.1',
51
- 'sphinxcontrib-jsmath==1.0.1',
52
- 'sphinxcontrib-qthelp==1.0.3',
53
- 'sphinxcontrib-serializinghtml==1.1.5',
54
- 'tomli==2.0.1',
55
- 'urllib3==2.0.3',
56
- 'yarg==0.1.9',
57
- 'yarl==1.9.2',
58
- 'zipp==3.16.2',
59
- 'aiofiles~=23.2.1',
60
- 'pydantic~=2.7.1'
61
- ]
62
-
63
- [project.urls]
64
- "Homepage" = "https://github.com/SyperAlexKomp/simple-rule34-api"
65
- "Bug Tracker" = "https://github.com/SyperAlexKomp/simple-rule34-api/issues"
@@ -1,151 +0,0 @@
1
- import random
2
- import time
3
-
4
- import aiohttp
5
- import datetime
6
- import logging
7
- import xml.etree.ElementTree as ET
8
-
9
- from .exceptions import *
10
- from .types import *
11
-
12
-
13
- class Rule34Api:
14
- def __init__(self, api_str: str):
15
- self.header = {'User-Agent': 'rule34-simple-api 0.1.5.6 (Request)'}
16
- self.api_str = api_str
17
-
18
- async def get_post_count(self, tags: str = '') -> int:
19
- async with aiohttp.ClientSession(headers=self.header) as session:
20
- async with session.get(f'https://api.rule34.xxx/index.php?'
21
- f'page=dapi&s=post&q=index&tags={tags}&{self.api_str}') as response:
22
- xml_data = await response.text()
23
-
24
- xml_root = ET.fromstring(xml_data)
25
-
26
- return int(xml_root.get('count'))
27
-
28
- async def get_post(self, id: int) -> Post:
29
- st = time.time()
30
-
31
- async with aiohttp.ClientSession(headers=self.header) as session:
32
- async with session.get(f'https://api.rule34.xxx/index.php?'
33
- f'json=1&page=dapi&s=post&q=index&id={id}&{self.api_str}') as response:
34
- if response.status != 200:
35
- raise ApiException(f"Api returned status code {response.status} with message"
36
- f" {await response.text()}")
37
-
38
- j = await response.json()
39
- data = j[0]
40
-
41
- data["main"] = {
42
- "url": data['file_url']
43
- }
44
- data["preview"] = {
45
- "url": data['preview_url']
46
- }
47
- data["tags"] = data["tags"].split(" ")
48
-
49
- logging.debug(f"Post[{id}] where found in {time.time() - st}s")
50
-
51
- return Post(**data)
52
-
53
- async def get_random_post(self, tags: str = '', forbidden_tags: list[str] = []) -> Post:
54
- start_time = time.time()
55
-
56
- post_count = await self.get_post_count(tags)
57
-
58
- page_count = post_count // 1000
59
-
60
- if page_count > 0:
61
- post_list = await self.get_post_list(page_id=random.randint(0, page_count if page_count <= 200 else 200),
62
- tags=tags, limit=1000)
63
-
64
- else:
65
- post_list = await self.get_post_list(tags=tags, limit=1000)
66
-
67
- post_list_ = []
68
-
69
- for post in post_list:
70
- if any(tag in forbidden_tags for tag in post.main.tags):
71
- pass
72
- else:
73
- post_list_.append(post)
74
-
75
- logging.debug(f"Random posts where found in {time.time() - start_time}s")
76
-
77
- return post_list_[random.randint(0, len(post_list_) - 1)] if len(post_list_) > 0 else None
78
-
79
- async def get_random_posts(self, tags: str = '', count: int = 8, forbidden_tags: list[str] = []) -> list[Post]:
80
- st = time.time()
81
-
82
- request_count = 1
83
- true_count = count*20
84
-
85
- post_list = []
86
-
87
- if true_count > 1000:
88
- request_count = true_count // 1000
89
-
90
- post_count = await self.get_post_count(tags)
91
- page_id = int(random.randint(0, int(post_count / true_count)) / 8) if post_count >= true_count else 0
92
-
93
- for pid in range(request_count + 1):
94
- post_list += await self.get_post_list(tags=tags, forbidden_tags=forbidden_tags,
95
- page_id=page_id, limit=true_count if true_count <= 1000 else 1000)
96
-
97
- getted = []
98
-
99
- for x in range(count):
100
- if len(post_list) > 0:
101
- getted.append(post_list[random.randint(0, len(post_list) - 1)])
102
- else:
103
- pass
104
-
105
- logging.debug(f"{count} random posts where found in {time.time() - st}s")
106
-
107
- return getted
108
-
109
- async def get_post_list(self, limit: int = 1000, page_id: int = 0, tags: str = '', forbidden_tags: list[str] = [])\
110
- -> list[Post]:
111
- if limit > 1000:
112
- raise ToBigRequestException(f"The max size of request is 1000 when you tried to request {limit}")
113
-
114
- async with aiohttp.ClientSession(headers=self.header) as session:
115
- start_time = time.time()
116
-
117
- async with session.get(f'https://api.rule34.xxx/index.php?'
118
- f'json=1&page=dapi&s=post&q=index&limit={limit}&pid={page_id}&tags={tags}&{self.api_str}') as response:
119
- if response.status != 200:
120
- raise ApiException(f"Api returned status code {response.status} with message"
121
- f" {await response.text()}")
122
-
123
- data = await response.json()
124
-
125
- logging.debug(f"Request with {limit} limit posts were done in {time.time() - start_time}s")
126
-
127
- post_list = []
128
- for post_data in data:
129
- post_data["main"] = {
130
- "url": post_data['file_url']
131
- }
132
- post_data["preview"] = {
133
- "url": post_data['preview_url']
134
- }
135
- post_data["tags"] = post_data["tags"].split(" ")
136
-
137
- post_list.append(Post(**post_data))
138
-
139
- start_time = time.time()
140
-
141
- post_list_ = []
142
-
143
- for post in post_list:
144
- if any(tag in forbidden_tags for tag in post.tags):
145
- pass
146
- else:
147
- post_list_.append(post)
148
-
149
- logging.debug(f"{len(post_list_)} posts where found in {time.time() - start_time}s")
150
-
151
- return post_list_
@@ -1 +0,0 @@
1
- from .Rule34 import Rule34Api
@@ -1,62 +0,0 @@
1
- import os
2
- import typing
3
- import aiofiles
4
- import aiohttp
5
-
6
- from aiofiles import os as aos
7
- from pydantic import BaseModel
8
-
9
- from .exceptions import ApiException
10
- from .utils import get_file_type
11
-
12
-
13
- class File(BaseModel):
14
- url: str
15
- type: str = None
16
-
17
- def __init__(self, /, **data: typing.Any) -> None:
18
- super().__init__(**data)
19
-
20
- self.type = get_file_type(self.url)
21
-
22
- async def download(self, path: str = r'./rule34_download') -> str:
23
- try:
24
- await aos.mkdir(path)
25
- except:
26
- pass
27
-
28
- async with aiohttp.ClientSession() as session:
29
- async with session.get(self.url) as response:
30
- if response.status != 200:
31
- raise ApiException(f"Api returned status code {response.status} with message"
32
- f" {await response.text()}")
33
-
34
- file_name = os.path.basename(self.url)
35
- save_path = os.path.join(path, file_name)
36
-
37
- async with aiofiles.open(save_path, 'wb') as file:
38
- await file.write(await response.read())
39
-
40
- return save_path
41
-
42
-
43
- class Post(BaseModel):
44
- directory: int
45
- hash: str
46
- width: int
47
- height: int
48
- id: int
49
- change: int
50
- owner: str
51
- parent_id: int
52
- rating: str
53
- sample: bool
54
- score: int
55
- tags: list
56
- source: str
57
- status: str
58
- has_notes: bool
59
- comment_count: int
60
-
61
- main: File
62
- preview: File
@@ -1,22 +0,0 @@
1
-
2
- async def get_file_size(url, session):
3
- async with session.head(url=url, allow_redirects=True) as response:
4
- if 'Content-Length' in response.headers:
5
- size = int(response.headers['Content-Length'])
6
- return size
7
- else:
8
- return None
9
-
10
-
11
- def get_file_type(url):
12
- file_extension = url.split('.')[-1].lower()
13
- if file_extension in ['jpg', 'jpeg', 'png']:
14
- return 'photo'
15
- elif file_extension in ['mp4', 'avi', 'mov']:
16
- return 'video'
17
- elif file_extension == 'gif':
18
- return 'animation'
19
- else:
20
- return None
21
-
22
-
@@ -1,58 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: simple_rule34
3
- Version: 0.1.6.1
4
- Summary: Simple api wrapper of rule34.xxx for python with asynchronous support
5
- Author-email: StarMan12 <author@example.com>
6
- Project-URL: Homepage, https://github.com/SyperAlexKomp/simple-rule34-api
7
- Project-URL: Bug Tracker, https://github.com/SyperAlexKomp/simple-rule34-api/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.9
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: aiohttp==3.8.4
15
- Requires-Dist: aiosignal==1.3.1
16
- Requires-Dist: alabaster==0.7.13
17
- Requires-Dist: async-timeout==4.0.2
18
- Requires-Dist: attrs==23.1.0
19
- Requires-Dist: Babel==2.9.1
20
- Requires-Dist: build==0.10.0
21
- Requires-Dist: certifi==2023.5.7
22
- Requires-Dist: charset-normalizer==3.2.0
23
- Requires-Dist: colorama==0.4.6
24
- Requires-Dist: docopt==0.6.2
25
- Requires-Dist: docutils==0.18.1
26
- Requires-Dist: frozenlist==1.4.0
27
- Requires-Dist: idna==3.4
28
- Requires-Dist: imagesize==1.4.1
29
- Requires-Dist: importlib-metadata==6.8.0
30
- Requires-Dist: Jinja2==3.1.2
31
- Requires-Dist: MarkupSafe==2.1.3
32
- Requires-Dist: multidict==6.0.4
33
- Requires-Dist: packaging==23.1
34
- Requires-Dist: pipreqs==0.4.13
35
- Requires-Dist: Pygments==2.15.1
36
- Requires-Dist: pyproject_hooks==1.0.0
37
- Requires-Dist: requests==2.31.0
38
- Requires-Dist: snowballstemmer==2.2.0
39
- Requires-Dist: Sphinx==6.2.1
40
- Requires-Dist: sphinx-rtd-theme==1.2.2
41
- Requires-Dist: sphinxcontrib-applehelp==1.0.4
42
- Requires-Dist: sphinxcontrib-devhelp==1.0.2
43
- Requires-Dist: sphinxcontrib-htmlhelp==2.0.1
44
- Requires-Dist: sphinxcontrib-jquery==4.1
45
- Requires-Dist: sphinxcontrib-jsmath==1.0.1
46
- Requires-Dist: sphinxcontrib-qthelp==1.0.3
47
- Requires-Dist: sphinxcontrib-serializinghtml==1.1.5
48
- Requires-Dist: tomli==2.0.1
49
- Requires-Dist: urllib3==2.0.3
50
- Requires-Dist: yarg==0.1.9
51
- Requires-Dist: yarl==1.9.2
52
- Requires-Dist: zipp==3.16.2
53
- Requires-Dist: aiofiles~=23.2.1
54
- Requires-Dist: pydantic~=2.7.1
55
- Dynamic: license-file
56
-
57
- # rule34-simple-api
58
- Simple api wrapper of rule34.xxx for python with asynchronous support
@@ -1,41 +0,0 @@
1
- aiohttp==3.8.4
2
- aiosignal==1.3.1
3
- alabaster==0.7.13
4
- async-timeout==4.0.2
5
- attrs==23.1.0
6
- Babel==2.9.1
7
- build==0.10.0
8
- certifi==2023.5.7
9
- charset-normalizer==3.2.0
10
- colorama==0.4.6
11
- docopt==0.6.2
12
- docutils==0.18.1
13
- frozenlist==1.4.0
14
- idna==3.4
15
- imagesize==1.4.1
16
- importlib-metadata==6.8.0
17
- Jinja2==3.1.2
18
- MarkupSafe==2.1.3
19
- multidict==6.0.4
20
- packaging==23.1
21
- pipreqs==0.4.13
22
- Pygments==2.15.1
23
- pyproject_hooks==1.0.0
24
- requests==2.31.0
25
- snowballstemmer==2.2.0
26
- Sphinx==6.2.1
27
- sphinx-rtd-theme==1.2.2
28
- sphinxcontrib-applehelp==1.0.4
29
- sphinxcontrib-devhelp==1.0.2
30
- sphinxcontrib-htmlhelp==2.0.1
31
- sphinxcontrib-jquery==4.1
32
- sphinxcontrib-jsmath==1.0.1
33
- sphinxcontrib-qthelp==1.0.3
34
- sphinxcontrib-serializinghtml==1.1.5
35
- tomli==2.0.1
36
- urllib3==2.0.3
37
- yarg==0.1.9
38
- yarl==1.9.2
39
- zipp==3.16.2
40
- aiofiles~=23.2.1
41
- pydantic~=2.7.1
@@ -1,40 +0,0 @@
1
- from SimpleRule34.src.SimpleRule34.Rule34 import Rule34Api
2
- from SimpleRule34.src.SimpleRule34 import Rule34Api
3
- import asyncio
4
-
5
-
6
- async def main1():
7
-
8
- r = Rule34Api()
9
- print("=====ALL 10=====")
10
- lis = await r.get_post_list(tags='', limit=10)
11
-
12
- for x in lis:
13
- print(x)
14
-
15
- print("=====FIRST 5=====")
16
- lis = await r.get_post_list(tags='brawl_stars', limit=5)
17
-
18
- for x in lis:
19
- print(x)
20
-
21
- print("=====SECOND 5=====")
22
- lis = await r.get_post_list(tags='brawl_stars', limit=5, page_id=1)
23
-
24
- for x in lis:
25
- print(x)
26
-
27
- print("=====EDITED=====")
28
- post_list_ = await r.get_post_list(tags='brawl_stars', limit=100, page_id=0,
29
- forbidden_tags=['male'])
30
-
31
- return
32
-
33
-
34
- async def main():
35
- r = Rule34Api()
36
- p = await r.get_post_list(limit=10)
37
- print(p)
38
-
39
- if __name__ == '__main__':
40
- asyncio.run(main())
File without changes