simple-rule34 0.1.6__tar.gz → 1.0.0.0__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.0/PKG-INFO +20 -0
  2. simple_rule34-1.0.0.0/pyproject.toml +27 -0
  3. simple_rule34-1.0.0.0/src/SimpleRule34/__init__.py +1 -0
  4. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/src/SimpleRule34/exceptions.py +0 -8
  5. simple_rule34-1.0.0.0/src/SimpleRule34/main.py +207 -0
  6. simple_rule34-1.0.0.0/src/SimpleRule34/types.py +125 -0
  7. simple_rule34-1.0.0.0/src/SimpleRule34/utils.py +31 -0
  8. simple_rule34-1.0.0.0/src/simple_rule34.egg-info/PKG-INFO +20 -0
  9. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/src/simple_rule34.egg-info/SOURCES.txt +1 -1
  10. simple_rule34-1.0.0.0/src/simple_rule34.egg-info/requires.txt +3 -0
  11. simple_rule34-1.0.0.0/tests/test.py +20 -0
  12. simple_rule34-0.1.6/PKG-INFO +0 -57
  13. simple_rule34-0.1.6/pyproject.toml +0 -65
  14. simple_rule34-0.1.6/src/SimpleRule34/Rule34.py +0 -149
  15. simple_rule34-0.1.6/src/SimpleRule34/__init__.py +0 -1
  16. simple_rule34-0.1.6/src/SimpleRule34/types.py +0 -62
  17. simple_rule34-0.1.6/src/SimpleRule34/utils.py +0 -22
  18. simple_rule34-0.1.6/src/simple_rule34.egg-info/PKG-INFO +0 -57
  19. simple_rule34-0.1.6/src/simple_rule34.egg-info/requires.txt +0 -41
  20. simple_rule34-0.1.6/tests/test.py +0 -40
  21. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/LICENSE +0 -0
  22. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/README.md +0 -0
  23. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/setup.cfg +0 -0
  24. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/src/__init__.py +0 -0
  25. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/src/simple_rule34.egg-info/dependency_links.txt +0 -0
  26. {simple_rule34-0.1.6 → simple_rule34-1.0.0.0}/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.0
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.0"
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,125 @@
1
+ import os
2
+ import typing
3
+ import re
4
+
5
+ from enum import Enum
6
+ from pathlib import Path
7
+
8
+ import aiofiles
9
+ import aiohttp
10
+
11
+ from pydantic import BaseModel, field_validator, HttpUrl, Field
12
+
13
+ from .exceptions import ApiException
14
+ from .utils import get_file_type
15
+
16
+
17
+ class File(BaseModel):
18
+ url: HttpUrl
19
+ type: str = None
20
+
21
+ def __init__(self, /, **data: typing.Any) -> None:
22
+ super().__init__(**data)
23
+
24
+ self.type = get_file_type(str(self.url))
25
+
26
+ async def download(self, path: Path | str = "./rule34_downloads") -> Path:
27
+ if isinstance(path, str):
28
+ path = Path(path)
29
+ # Create storage path
30
+ path.mkdir(parents=True, exist_ok=True)
31
+
32
+ async with aiohttp.ClientSession() as session:
33
+ async with session.get(str(self.url)) 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
+ file_name = os.path.basename(str(self.url))
39
+ save_path = path / file_name
40
+
41
+ async with aiofiles.open(save_path, 'wb') as file:
42
+ await file.write(await response.read())
43
+
44
+ return save_path
45
+
46
+
47
+ class Rule34Post(BaseModel):
48
+ id: int
49
+ owner: str | None
50
+ status: str | None
51
+ rating: str | None
52
+ score: int | None
53
+
54
+ preview_file: File = Field(validation_alias="preview_url")
55
+ sample_file: File = Field(validation_alias="sample_url")
56
+ file: File = Field(validation_alias="file_url")
57
+ source: str | None
58
+
59
+ width: int | None
60
+ height: int | None
61
+ hash: str | None
62
+ image: str | None
63
+ directory: int | None
64
+
65
+ change: int | None
66
+ parent_id: int | None
67
+ has_notes: bool | None
68
+ comment_count: int | None
69
+
70
+ sample: bool | None
71
+ sample_height: int | None
72
+ sample_width: int | None
73
+
74
+ tags: typing.List[str] | None
75
+
76
+ @field_validator('tags', mode='before')
77
+ @classmethod
78
+ def split_tags(cls, v):
79
+ if isinstance(v, str):
80
+ return v.strip().split()
81
+ return v
82
+
83
+ @field_validator('preview_file', 'sample_file', 'file', mode='before', check_fields=False)
84
+ @classmethod
85
+ def wrap_in_file(cls, v):
86
+ if isinstance(v, (str, HttpUrl)):
87
+ return File(url=v)
88
+ return v
89
+
90
+ class Config:
91
+ populate_by_name = True
92
+
93
+
94
+ class Rule34Comment(BaseModel):
95
+ id: int
96
+ post_id: int
97
+ message: str = Field(alias="body")
98
+ creator: str
99
+ creator_id: int
100
+
101
+
102
+ class Rule34TagType(Enum):
103
+ GENERAL = "0"
104
+ ARTIST = "1"
105
+ COPYRIGHT = "3"
106
+ UNKNOWN = "2"
107
+ CHARACTER = "4"
108
+ META = "5"
109
+
110
+
111
+ class Rule34Tag(BaseModel):
112
+ id: int
113
+ type: Rule34TagType
114
+ name: str
115
+ count: int = Field(default_factory=int)
116
+ ambiguous: bool
117
+
118
+
119
+ class Rule34Autocomplete(BaseModel):
120
+ label: str
121
+ value: str
122
+
123
+ @property
124
+ def count(self) -> int:
125
+ 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.0
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,20 @@
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
+ while True:
13
+ i = input("Enter tag: ")
14
+ for x in await api.autocomplete.search(text=i):
15
+ print(x)
16
+
17
+
18
+
19
+ if __name__ == '__main__':
20
+ asyncio.run(main())
@@ -1,57 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: simple_rule34
3
- Version: 0.1.6
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
-
56
- # rule34-simple-api
57
- 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"
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,149 +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):
15
- self.header = {'User-Agent': 'rule34-simple-api 0.1.5.6 (Request)'}
16
- async def get_post_count(self, tags: str = '') -> int:
17
- async with aiohttp.ClientSession(headers=self.header) as session:
18
- async with session.get(f'https://api.rule34.xxx/index.php?'
19
- f'page=dapi&s=post&q=index&tags={tags}') as response:
20
- xml_data = await response.text()
21
-
22
- xml_root = ET.fromstring(xml_data)
23
-
24
- return int(xml_root.get('count'))
25
-
26
- async def get_post(self, id: int) -> Post:
27
- st = time.time()
28
-
29
- async with aiohttp.ClientSession(headers=self.header) as session:
30
- async with session.get(f'https://api.rule34.xxx/index.php?'
31
- f'json=1&page=dapi&s=post&q=index&id={id}') as response:
32
- if response.status != 200:
33
- raise ApiException(f"Api returned status code {response.status} with message"
34
- f" {await response.text()}")
35
-
36
- j = await response.json()
37
- data = j[0]
38
-
39
- data["main"] = {
40
- "url": data['file_url']
41
- }
42
- data["preview"] = {
43
- "url": data['preview_url']
44
- }
45
- data["tags"] = data["tags"].split(" ")
46
-
47
- logging.debug(f"Post[{id}] where found in {time.time() - st}s")
48
-
49
- return Post(**data)
50
-
51
- async def get_random_post(self, tags: str = '', forbidden_tags: list[str] = []) -> Post:
52
- start_time = time.time()
53
-
54
- post_count = await self.get_post_count(tags)
55
-
56
- page_count = post_count // 1000
57
-
58
- if page_count > 0:
59
- post_list = await self.get_post_list(page_id=random.randint(0, page_count if page_count <= 200 else 200),
60
- tags=tags, limit=1000)
61
-
62
- else:
63
- post_list = await self.get_post_list(tags=tags, limit=1000)
64
-
65
- post_list_ = []
66
-
67
- for post in post_list:
68
- if any(tag in forbidden_tags for tag in post.main.tags):
69
- pass
70
- else:
71
- post_list_.append(post)
72
-
73
- logging.debug(f"Random posts where found in {time.time() - start_time}s")
74
-
75
- return post_list_[random.randint(0, len(post_list_) - 1)] if len(post_list_) > 0 else None
76
-
77
- async def get_random_posts(self, tags: str = '', count: int = 8, forbidden_tags: list[str] = []) -> list[Post]:
78
- st = time.time()
79
-
80
- request_count = 1
81
- true_count = count*20
82
-
83
- post_list = []
84
-
85
- if true_count > 1000:
86
- request_count = true_count // 1000
87
-
88
- post_count = await self.get_post_count(tags)
89
- page_id = int(random.randint(0, int(post_count / true_count)) / 8) if post_count >= true_count else 0
90
-
91
- for pid in range(request_count + 1):
92
- post_list += await self.get_post_list(tags=tags, forbidden_tags=forbidden_tags,
93
- page_id=page_id, limit=true_count if true_count <= 1000 else 1000)
94
-
95
- getted = []
96
-
97
- for x in range(count):
98
- if len(post_list) > 0:
99
- getted.append(post_list[random.randint(0, len(post_list) - 1)])
100
- else:
101
- pass
102
-
103
- logging.debug(f"{count} random posts where found in {time.time() - st}s")
104
-
105
- return getted
106
-
107
- async def get_post_list(self, limit: int = 1000, page_id: int = 0, tags: str = '', forbidden_tags: list[str] = [])\
108
- -> list[Post]:
109
- if limit > 1000:
110
- raise ToBigRequestException(f"The max size of request is 1000 when you tried to request {limit}")
111
-
112
- async with aiohttp.ClientSession(headers=self.header) as session:
113
- start_time = time.time()
114
-
115
- async with session.get(f'https://api.rule34.xxx/index.php?'
116
- f'json=1&page=dapi&s=post&q=index&limit={limit}&pid={page_id}&tags={tags}') as response:
117
- if response.status != 200:
118
- raise ApiException(f"Api returned status code {response.status} with message"
119
- f" {await response.text()}")
120
-
121
- data = await response.json()
122
-
123
- logging.debug(f"Request with {limit} limit posts were done in {time.time() - start_time}s")
124
-
125
- post_list = []
126
- for post_data in data:
127
- post_data["main"] = {
128
- "url": post_data['file_url']
129
- }
130
- post_data["preview"] = {
131
- "url": post_data['preview_url']
132
- }
133
- post_data["tags"] = post_data["tags"].split(" ")
134
-
135
- post_list.append(Post(**post_data))
136
-
137
- start_time = time.time()
138
-
139
- post_list_ = []
140
-
141
- for post in post_list:
142
- if any(tag in forbidden_tags for tag in post.tags):
143
- pass
144
- else:
145
- post_list_.append(post)
146
-
147
- logging.debug(f"{len(post_list_)} posts where found in {time.time() - start_time}s")
148
-
149
- 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,57 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: simple_rule34
3
- Version: 0.1.6
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
-
56
- # rule34-simple-api
57
- 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
File without changes
File without changes