simple-rule34 0.1.6__py3-none-any.whl → 1.0.0.0__py3-none-any.whl

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.
SimpleRule34/__init__.py CHANGED
@@ -1 +1 @@
1
- from .Rule34 import Rule34Api
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
SimpleRule34/main.py ADDED
@@ -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)
SimpleRule34/types.py CHANGED
@@ -1,38 +1,42 @@
1
1
  import os
2
2
  import typing
3
+ import re
4
+
5
+ from enum import Enum
6
+ from pathlib import Path
7
+
3
8
  import aiofiles
4
9
  import aiohttp
5
10
 
6
- from aiofiles import os as aos
7
- from pydantic import BaseModel
11
+ from pydantic import BaseModel, field_validator, HttpUrl, Field
8
12
 
9
13
  from .exceptions import ApiException
10
14
  from .utils import get_file_type
11
15
 
12
16
 
13
17
  class File(BaseModel):
14
- url: str
18
+ url: HttpUrl
15
19
  type: str = None
16
20
 
17
21
  def __init__(self, /, **data: typing.Any) -> None:
18
22
  super().__init__(**data)
19
23
 
20
- self.type = get_file_type(self.url)
24
+ self.type = get_file_type(str(self.url))
21
25
 
22
- async def download(self, path: str = r'./rule34_download') -> str:
23
- try:
24
- await aos.mkdir(path)
25
- except:
26
- pass
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)
27
31
 
28
32
  async with aiohttp.ClientSession() as session:
29
- async with session.get(self.url) as response:
33
+ async with session.get(str(self.url)) as response:
30
34
  if response.status != 200:
31
35
  raise ApiException(f"Api returned status code {response.status} with message"
32
36
  f" {await response.text()}")
33
37
 
34
- file_name = os.path.basename(self.url)
35
- save_path = os.path.join(path, file_name)
38
+ file_name = os.path.basename(str(self.url))
39
+ save_path = path / file_name
36
40
 
37
41
  async with aiofiles.open(save_path, 'wb') as file:
38
42
  await file.write(await response.read())
@@ -40,23 +44,82 @@ class File(BaseModel):
40
44
  return save_path
41
45
 
42
46
 
43
- class Post(BaseModel):
44
- directory: int
45
- hash: str
46
- width: int
47
- height: int
47
+ class Rule34Post(BaseModel):
48
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
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))
SimpleRule34/utils.py CHANGED
@@ -1,3 +1,16 @@
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
+
1
14
 
2
15
  async def get_file_size(url, session):
3
16
  async with session.head(url=url, allow_redirects=True) as response:
@@ -8,15 +21,11 @@ async def get_file_size(url, session):
8
21
  return None
9
22
 
10
23
 
11
- def get_file_type(url):
24
+ def get_file_type(url) -> str | None:
12
25
  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
26
+
27
+ if file_extension not in FILE_TYPES.keys(): return None
28
+
29
+ return FILE_TYPES[file_extension]
21
30
 
22
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
@@ -0,0 +1,11 @@
1
+ __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ SimpleRule34/__init__.py,sha256=9VMHjQsFOoSFNj_Ufq6VJLcTXHJPvqxeEozHpIpXSe0,29
3
+ SimpleRule34/exceptions.py,sha256=7tjDPPoOmXTBaMbfsSjaLsXkzgChdKLBsDyCMClMIAo,341
4
+ SimpleRule34/main.py,sha256=L_JIw45Y8UcBWiRvmeeB5zSimjHLOjVoNf1Hg29rMKY,7080
5
+ SimpleRule34/types.py,sha256=6yHumh_U2khFmxgBzVyz2Hj81kdH-z6vCOqfoPHyj0k,3127
6
+ SimpleRule34/utils.py,sha256=q4ohygRV4J88mNUQnCQ_-_A2-syIZpnO_UifIt_vpFc,689
7
+ simple_rule34-1.0.0.0.dist-info/licenses/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
8
+ simple_rule34-1.0.0.0.dist-info/METADATA,sha256=gh6Aa2UwAXDGHe3uSYxP24r1NnrdqKcZmMEXayx0DdE,801
9
+ simple_rule34-1.0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ simple_rule34-1.0.0.0.dist-info/top_level.txt,sha256=YI-Z1ijzIjlJw2WeM95PSmClCzIvm24KAlyZi80YVNs,22
11
+ simple_rule34-1.0.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
SimpleRule34/Rule34.py DELETED
@@ -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,186 +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
- async def parse_result(post_element):
14
-
15
-
16
- id = int(post_element.get('id'))
17
- height = int(post_element.get('height'))
18
- width = int(post_element.get('width'))
19
- url = post_element.get('file_url')
20
-
21
- sample_height = int(post_element.get('sample_height'))
22
- sample_width = int(post_element.get('sample_width'))
23
- sample_url = post_element.get('sample_url')
24
-
25
- sample_post = Rule34SamplePost(sample_height, sample_width, sample_url, id)
26
-
27
- preview_height = int(post_element.get('preview_height'))
28
- preview_width = int(post_element.get('preview_width'))
29
- preview_url = post_element.get('preview_url')
30
-
31
- preview_post = Rule34PreviewPost(preview_height, preview_width, preview_url, id)
32
-
33
- score = int(post_element.get('score'))
34
- rating = post_element.get('rating')
35
- creator_id = int(post_element.get('creator_id'))
36
- tags = post_element.get('tags')
37
- has_children = post_element.get('has_children') == 'true'
38
- created_date = datetime.datetime.strptime(post_element.get('created_at'), "%a %b %d %H:%M:%S %z %Y")
39
- status = post_element.get('status')
40
- source = post_element.get('source')
41
- has_notes = post_element.get('has_notes') == 'true'
42
- has_comments = post_element.get('has_comments') == 'true'
43
-
44
- main_post = Rule34MainPost(score, rating, creator_id, tags, has_children, created_date, status,
45
- source, has_notes, has_comments, height, width, url, id)
46
-
47
- return main_post, sample_post, preview_post
48
-
49
- class Rule34Api:
50
- def __init__(self):
51
- self.header = {'User-Agent': 'rule34-simple-api 0.1.5.3 (Request)'}
52
- async def get_post_count(self, tags: str = '') -> int:
53
- async with aiohttp.ClientSession(headers=self.header) as session:
54
- async with session.get(f'https://api.rule34.xxx/index.php?'
55
- f'page=dapi&s=post&q=index&tags={tags}') as response:
56
- xml_data = await response.text()
57
-
58
- xml_root = ET.fromstring(xml_data)
59
-
60
- return int(xml_root.get('count'))
61
-
62
- async def get_post(self, id: int):
63
- st = time.time()
64
-
65
- async with aiohttp.ClientSession(headers=self.header) as session:
66
- async with session.get(f'https://api.rule34.xxx/index.php?'
67
- f'page=dapi&s=post&q=index&id={id}') as response:
68
- xml_data = await response.text()
69
-
70
- try:
71
- xml_root = ET.fromstring(xml_data)
72
- except:
73
- return None
74
-
75
- post_element = xml_root.find('post')
76
-
77
- parsed = await parse_result(post_element)
78
-
79
- if parsed is None:
80
- return None
81
- else:
82
- main_post, sample_post, preview_post = parsed
83
-
84
- logging.info(f"Post where found in {time.time() - st}s")
85
-
86
- return Rule34PostData(id, main_post, sample_post, preview_post)
87
-
88
- async def get_random_post(self, tags: str = '', forbidden_tags: list[str] = []):
89
- start_time = time.time()
90
-
91
- post_count = await self.get_post_count(tags)
92
-
93
- page_count = post_count // 1000
94
-
95
- if page_count > 0:
96
- post_list = await self.get_post_list(page_id=random.randint(0, page_count if page_count <= 200 else 200),
97
- tags=tags, limit=1000)
98
-
99
- else:
100
- post_list = await self.get_post_list(tags=tags, limit=1000)
101
-
102
- post_list_ = []
103
-
104
- for post in post_list:
105
- if any(tag in forbidden_tags for tag in post.main.tags):
106
- pass
107
- else:
108
- post_list_.append(post)
109
-
110
- logging.info(f"Random post where found in {time.time() - start_time}s")
111
-
112
- return post_list_[random.randint(0, len(post_list_) - 1)] if len(post_list_) > 0 else None
113
-
114
- async def get_random_posts(self, tags: str = '', count: int = 8, forbidden_tags: list[str] = []) -> list[Rule34PostData]:
115
- st = time.time()
116
-
117
- request_count = 1
118
- true_count = count*20
119
-
120
- post_list = []
121
-
122
- if true_count > 1000:
123
- request_count = true_count // 1000
124
-
125
- post_count = await self.get_post_count(tags)
126
- page_id = int(random.randint(0, int(post_count / true_count)) / 8) if post_count >= true_count else 0
127
-
128
- for pid in range(request_count + 1):
129
- post_list += await self.get_post_list(tags=tags, forbidden_tags=forbidden_tags,
130
- page_id=page_id, limit=true_count if true_count <= 1000 else 1000)
131
-
132
- getted = []
133
-
134
- for x in range(count):
135
- if len(post_list) > 0:
136
- getted.append(post_list[random.randint(0, len(post_list) - 1)])
137
- else:
138
- pass
139
-
140
- logging.info(f"{count} random posts where found in {time.time() - st}s")
141
-
142
- return getted
143
-
144
- async def get_post_list(self, limit: int = 1000, page_id: int = 0, tags: str = '', forbidden_tags: list[str] = [])\
145
- -> list[Rule34PostData]:
146
- async with aiohttp.ClientSession(headers=self.header) as session:
147
- if limit > 1000:
148
- raise ToBigRequestException(f"The max size of request is 1000 when you tried to request {limit}")
149
-
150
- start_time = time.time()
151
-
152
- async with session.get(f'https://api.rule34.xxx/index.php?'
153
- f'page=dapi&s=post&q=index&limit={limit}&pid={page_id}&tags={tags}') as response:
154
- xml_data = await response.text()
155
-
156
- logging.debug(f"Request with {limit} limit posts were done in {time.time() - start_time}s")
157
-
158
- xml_root = ET.fromstring(xml_data)
159
- posts = xml_root.findall('post')
160
- post_list = []
161
-
162
- start_time = time.time()
163
-
164
- for post_element in posts:
165
- parsed = await parse_result(post_element)
166
-
167
- if parsed is None:
168
- return []
169
- else:
170
- main_post, sample_post, preview_post = parsed
171
-
172
- post_list.append(Rule34PostData(main_post.id, main_post, sample_post, preview_post))
173
-
174
- logging.debug(f"Creating {len(posts)} objects was done in {time.time() - start_time}s")
175
-
176
- post_list_ = []
177
-
178
- for post in post_list:
179
- if any(tag in forbidden_tags for tag in post.main.tags):
180
- pass
181
- else:
182
- post_list_.append(post)
183
-
184
- logging.info(f"{len(post_list_)} posts where found in {time.time() - start_time}s")
185
-
186
- return post_list_
SimpleRule34/aio/types.py DELETED
@@ -1,98 +0,0 @@
1
- import typing
2
-
3
- import aiofiles
4
- import aiohttp
5
- import os
6
- import datetime
7
-
8
- from .utils import get_file_size, get_file_type
9
-
10
-
11
- class Rule34Post:
12
- def __init__(self, height: int, width: int, url: str, id: int):
13
- self.path = r'./rule34_download'
14
- self.height = height
15
- self.width = width
16
- self.url = url
17
- self.id = id
18
- self.file_type = get_file_type(self.url)
19
-
20
- def __str__(self):
21
- return f"<Rule34Post(id={self.id}, height={self.height}, width={self.width}, url={self.url})>"
22
-
23
- async def get_file_size(self) -> typing.Optional[int]:
24
- async with aiohttp.ClientSession() as session:
25
- file_size = await get_file_size(self.url, session)
26
- return file_size
27
-
28
- async def download(self, path=r'./rule34_download'):
29
- async with aiohttp.ClientSession() as session:
30
- async with session.get(self.url) as response:
31
- if response.status == 200:
32
- try:
33
- os.mkdir(self.path)
34
- except:
35
- pass
36
-
37
- file_name = os.path.basename(self.url)
38
- save_path = os.path.join(path, file_name)
39
- async with aiofiles.open(save_path, 'wb') as file:
40
- await file.write(await response.read())
41
-
42
- return save_path
43
- else:
44
- pass
45
-
46
- async def get_bytes(self):
47
- async with aiohttp.ClientSession() as session:
48
- async with session.get(self.url) as response:
49
- return await response.read()
50
-
51
-
52
- class Rule34MainPost(Rule34Post):
53
- def __init__(self, score: int, rating: str, creator_id: int, tags: str, has_children: bool,
54
- created_date: datetime.datetime, status: str, source: str, has_notes: bool, has_comments: bool,
55
- height: int, width: int, url: str, id: int):
56
- super().__init__(height, width, url, id)
57
- self.score = score
58
- self.rating = rating
59
- self.creator_id = creator_id
60
- self.tags = tags.split(" ")
61
- self.has_children = has_children
62
- self.created_date = created_date
63
- self.status = status
64
- self.source = source
65
- self.has_notes = has_notes
66
- self.has_comments = has_comments
67
-
68
- def __str__(self):
69
- return f"<Rule34MainPost(id={self.id}, height={self.height}, width={self.width}, url={self.url}," \
70
- f" score={self.score}, rating={self.rating}, creator_id={self.creator_id}," \
71
- f" has_children={self.has_children}, has_notes={self.has_notes}, has_comments={self.has_comments}" \
72
- f" created_date={self.created_date}, status={self.status}, tags={self.tags})>"
73
-
74
- def format_tags(self, format: str = "#"):
75
- str_tags = (format + ' ').join(self.tags)
76
- return str_tags.split(" ")
77
-
78
-
79
- class Rule34SamplePost(Rule34Post):
80
- def __str__(self):
81
- return f"<Rule34SamplePost(id={self.id}, height={self.height}, width={self.width}, url={self.url})>"
82
-
83
-
84
- class Rule34PreviewPost(Rule34Post):
85
- def __str__(self):
86
- return f"<Rule34PreviewPost(id={self.id}, height={self.height}, width={self.width}, url={self.url})>"
87
-
88
-
89
- class Rule34PostData:
90
- def __init__(self, id: int, main: Rule34MainPost, sample: Rule34SamplePost, preview: Rule34PreviewPost):
91
- self.id = id
92
- self.main = main
93
- self.sample = sample
94
- self.preview = preview
95
-
96
- def __str__(self):
97
- return f"<Rule34PostData(id={self.id}, main={str(self.main)}, sample={str(self.sample)}," \
98
- f" preview={str(self.preview)})>"
SimpleRule34/aio/utils.py DELETED
@@ -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
-
SimpleRule34/setup.py DELETED
@@ -1,26 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- setup(
4
- name='SimpleRule34',
5
- version='0.1',
6
- description='Simple api wrapper of rule34.xxx for python with asynchronous support',
7
- packages=find_packages(),
8
- install_requires=[
9
- 'aiohttp==3.8.4',
10
- 'aiosignal==1.3.1',
11
- 'async-timeout==4.0.2',
12
- 'attrs==23.1.0',
13
- 'certifi==2023.5.7',
14
- 'charset-normalizer==3.2.0',
15
- 'docopt==0.6.2',
16
- 'frozenlist==1.4.0',
17
- 'idna==3.4',
18
- 'multidict==6.0.4',
19
- 'pipreqs==0.4.13',
20
- 'requests==2.31.0',
21
- 'urllib3==2.0.3',
22
- 'yarg==0.1.9',
23
- 'yarl==1.9.2',
24
- ],
25
- url='https://github.com/Loshok229/rule34-simple-api'
26
- )
@@ -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,15 +0,0 @@
1
- __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- SimpleRule34/Rule34.py,sha256=yVgZFMuCyC8VHbhxDWO37DnMvodkh9d8RYNRVtaHnIk,5433
3
- SimpleRule34/__init__.py,sha256=C4IbcJ_rjqBhpENXImVGuEQxf81GUwNaycqNNAY_ROc,31
4
- SimpleRule34/exceptions.py,sha256=dUpJBROzXWT67ZOLAHWWse0srvMRunIa9rSl1Cs10B8,491
5
- SimpleRule34/setup.py,sha256=7hNDTD52EAs7W00-Nd5LzvH8H4-Dvc59W8a999LqvW4,735
6
- SimpleRule34/types.py,sha256=n9y60b96g9wyD8FAZ_ElETBFNnM1F1GAs5VoxzCFI68,1518
7
- SimpleRule34/utils.py,sha256=fWx-ntJ672NoktH5Ws9-FcO0gtCNVmd8VfxFxjWqsD0,616
8
- SimpleRule34/aio/ARule34.py,sha256=nZwxmMNo_g_a16U9dJmxFGUIkDtgNB-W0uFH8LJ8tSI,6858
9
- SimpleRule34/aio/types.py,sha256=919XQt5ydRoLeVxy5HRujc0IeKYlD3crYuH_qvPqdKY,3719
10
- SimpleRule34/aio/utils.py,sha256=fWx-ntJ672NoktH5Ws9-FcO0gtCNVmd8VfxFxjWqsD0,616
11
- simple_rule34-0.1.6.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
12
- simple_rule34-0.1.6.dist-info/METADATA,sha256=dDBVIIRhZOzlaayIKolcj74YeEzKpsdGWWPi5bhQ_44,2154
13
- simple_rule34-0.1.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
14
- simple_rule34-0.1.6.dist-info/top_level.txt,sha256=YI-Z1ijzIjlJw2WeM95PSmClCzIvm24KAlyZi80YVNs,22
15
- simple_rule34-0.1.6.dist-info/RECORD,,