aioqzone 1.8.4.dev3__tar.gz → 1.8.6.dev1__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 (52) hide show
  1. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/PKG-INFO +8 -8
  2. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/pyproject.toml +8 -8
  3. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/api/h5/model.py +39 -12
  4. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/api/feed.py +12 -5
  5. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/api/request.py +20 -1
  6. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/api/response.py +2 -1
  7. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/LICENSE +0 -0
  8. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/README.md +0 -0
  9. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/__init__.py +0 -0
  10. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/api/__init__.py +0 -0
  11. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/api/h5/__init__.py +0 -0
  12. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/api/login/__init__.py +0 -0
  13. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/api/login/_base.py +0 -0
  14. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/exception.py +0 -0
  15. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/message.py +0 -0
  16. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/__init__.py +0 -0
  17. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/api/__init__.py +0 -0
  18. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/api/profile.py +0 -0
  19. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/protocol/__init__.py +0 -0
  20. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/protocol/config.py +0 -0
  21. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/model/protocol/entity.py +0 -0
  22. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/utils/__init__.py +0 -0
  23. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/utils/entity.py +0 -0
  24. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/utils/regex.py +0 -0
  25. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/utils/retry.py +0 -0
  26. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/aioqzone/utils/time.py +0 -0
  27. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/__init__.py +0 -0
  28. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/base.py +0 -0
  29. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/constant.py +0 -0
  30. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/exception.py +0 -0
  31. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/message.py +0 -0
  32. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/py.typed +0 -0
  33. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/qr/__init__.py +0 -0
  34. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/qr/type.py +0 -0
  35. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/type.py +0 -0
  36. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/__init__.py +0 -0
  37. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/_model.py +0 -0
  38. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/__init__.py +0 -0
  39. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/_model.py +0 -0
  40. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/capsess.py +0 -0
  41. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/pil_utils.py +0 -0
  42. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/select/__init__.py +0 -0
  43. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/select/_types.py +0 -0
  44. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/slide/__init__.py +0 -0
  45. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/captcha/slide/_types.py +0 -0
  46. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/encrypt.py +0 -0
  47. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/h5.py +0 -0
  48. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/up/web.py +0 -0
  49. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/utils/encrypt.py +0 -0
  50. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/utils/iter.py +0 -0
  51. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/utils/jsjson.py +0 -0
  52. {aioqzone-1.8.4.dev3 → aioqzone-1.8.6.dev1}/src/qqqr/utils/net.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioqzone
3
- Version: 1.8.4.dev3
3
+ Version: 1.8.6.dev1
4
4
  Summary: A Python wrapper for Qzone login and H5 APIs.
5
5
  Home-page: https://github.com/aioqzone/aioqzone
6
6
  License: AGPL-3.0
@@ -20,16 +20,16 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Typing :: Typed
22
22
  Provides-Extra: slide-captcha
23
- Requires-Dist: aiohttp (>=3.8.5,<4.0.0)
23
+ Requires-Dist: aiohttp (>=3.9.0,<4.0.0)
24
24
  Requires-Dist: exceptiongroup (>=1.1.1,<2.0.0)
25
- Requires-Dist: pillow (>=10.0.1,<11.0.0)
26
- Requires-Dist: pychaosvm (>=0.3.4,<0.5.0)
27
- Requires-Dist: pydantic (>=2.0.3,<3.0.0)
28
- Requires-Dist: pydantic-settings (>=2.0.2,<3.0.0)
25
+ Requires-Dist: pillow (>=10.1.0,<11.0.0)
26
+ Requires-Dist: pychaosvm (>=0.4.0,<0.5.0)
27
+ Requires-Dist: pydantic (>=2.5.0,<3.0.0)
28
+ Requires-Dist: pydantic-settings (>=2.2.0,<3.0.0)
29
29
  Requires-Dist: rsa (>=4.8,<5.0)
30
30
  Requires-Dist: slide-tc (>=0.1.1,<0.2.0) ; extra == "slide-captcha"
31
- Requires-Dist: tenacity (>=8.2.3,<9.0.0)
32
- Requires-Dist: tylisten (>=2.1.3,<3.0.0)
31
+ Requires-Dist: tenacity (>=8.2.3,<10.0.0)
32
+ Requires-Dist: tylisten (>=2.1.4,<3.0.0)
33
33
  Project-URL: Bug Tracker, https://github.com/aioqzone/aioqzone/issues
34
34
  Project-URL: Documentation, https://aioqzone.github.io/aioqzone
35
35
  Project-URL: Discussion, https://t.me/aioqzone_chatroom
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aioqzone"
3
- version = "1.8.4.dev3"
3
+ version = "1.8.6.dev1"
4
4
  description = "A Python wrapper for Qzone login and H5 APIs."
5
5
  authors = ["aioqzone <zzzzss990315@gmail.com>"]
6
6
  license = "AGPL-3.0"
@@ -28,15 +28,15 @@ exclude = ["*.js"]
28
28
 
29
29
  [tool.poetry.dependencies]
30
30
  python = "^3.8"
31
- aiohttp = "^3.8.5"
32
- pydantic = "^2.0.3"
33
- pydantic-settings = "^2.0.2"
31
+ aiohttp = "^3.9.0"
32
+ pydantic = "^2.5.0"
33
+ pydantic-settings = "^2.2.0"
34
34
  rsa = "^4.8"
35
- tenacity = "^8.2.3"
35
+ tenacity = ">=8.2.3,<10.0.0"
36
36
  exceptiongroup = "^1.1.1"
37
- tylisten = "^2.1.3"
38
- pillow = "^10.0.1"
39
- pychaosvm = { version = ">=0.3.4,<0.5.0", source = "aioqzone-index" }
37
+ tylisten = "^2.1.4"
38
+ pillow = "^10.1.0"
39
+ pychaosvm = { version = "~0.4.0", source = "aioqzone-index" }
40
40
  slide-tc = {version = "~0.1.1", optional = true, source = "aioqzone-index" }
41
41
 
42
42
  [tool.poetry.extras]
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from os import PathLike
2
3
 
3
4
  from pydantic import ValidationError
4
5
  from tenacity import AsyncRetrying, TryAgain, after_log, stop_after_attempt
@@ -177,7 +178,7 @@ class QzoneH5API:
177
178
  async def publish_mood(
178
179
  self,
179
180
  content: str,
180
- photos: t.Optional[t.List[PhotoData]] = None,
181
+ photos: t.Optional[t.Sequence[t.Union[PhotoData, PicInfo]]] = None,
181
182
  sync_weibo=False,
182
183
  ugc_right: UgcRight = UgcRight.all,
183
184
  ) -> PublishMoodResp:
@@ -189,21 +190,47 @@ class QzoneH5API:
189
190
  :param ugc_right: access right, default to "Available to Everyone".
190
191
  """
191
192
  photos = photos or []
192
- return await self.call(PublishMoodApi(params=PublishMoodParams.model_validate(locals())))
193
+ return await self.call(
194
+ PublishMoodApi(params=PublishMoodParams.model_validate(locals(), from_attributes=True))
195
+ )
193
196
 
194
197
  async def upload_pic(
195
- self, picture: bytes, width: int, height: int, quality: int
198
+ self,
199
+ picture: t.Union[bytes, str, PathLike, t.IO[bytes]],
200
+ width: t.Optional[int] = None,
201
+ height: t.Optional[int] = None,
202
+ quality: t.Union[int, float] = 70,
196
203
  ) -> UploadPicResponse:
197
- return await self.call(
198
- UploadPicApi(
199
- params=UploadPicParams(
200
- picture=picture,
201
- hd_width=width,
202
- hd_height=height,
203
- hd_quality=quality,
204
- ),
204
+ """
205
+ .. versionchanged:: 1.8.5
206
+
207
+ In version <= 1.8.4, user is responsible for compressing a image and this api
208
+ encode the :obj:`picture` with Base64 and send it to Qzone _ASIS_.
209
+
210
+ Since version 1.8.5, we recognize a compressed image by :obj:`width` and :obj:`height`
211
+ parameters. If :obj:`width` and :obj:`height` is provided, this API will keep the former
212
+ behavior. If not provided, the image will be compressed with the given quality.
213
+ """
214
+ if isinstance(quality, float):
215
+ if quality < 1:
216
+ quality *= 100
217
+ quality = int(quality)
218
+
219
+ assert 0 < quality <= 100
220
+
221
+ if isinstance(picture, (str, PathLike, t.IO)):
222
+ params = UploadPicParams.from_image(picture, quality)
223
+ elif (width is None) or (height is None):
224
+ params = UploadPicParams.from_bytes(picture, quality)
225
+ else:
226
+ params = UploadPicParams(
227
+ picture=picture,
228
+ hd_width=width,
229
+ hd_height=height,
230
+ hd_quality=quality,
205
231
  )
206
- )
232
+
233
+ return await self.call(UploadPicApi(params=params))
207
234
 
208
235
  async def preupload_photos(
209
236
  self, upload_pics: t.List[UploadPicResponse], cur_num=0, upload_hd=False
@@ -8,6 +8,7 @@ from pydantic import (
8
8
  BaseModel,
9
9
  Field,
10
10
  HttpUrl,
11
+ RootModel,
11
12
  field_validator,
12
13
  model_validator,
13
14
  )
@@ -97,21 +98,27 @@ class PhotoUrl(BaseModel):
97
98
  and (o.height == self.height)
98
99
  )
99
100
 
101
+ @property
102
+ def area(self):
103
+ return self.height * self.width
100
104
 
101
- class PhotoUrls(BaseModel):
102
- urls: t.Set[PhotoUrl]
103
105
 
106
+ class PhotoUrls(RootModel[t.Set[PhotoUrl]]):
104
107
  @model_validator(mode="before")
105
108
  def unpack_urls(cls, v: dict):
106
- return dict(urls=list(v.values()))
109
+ return list(v.values())
107
110
 
108
111
  @property
109
112
  def largest(self) -> PhotoUrl:
110
- return max(self.urls, key=lambda p: p.height * p.width)
113
+ return max(self.root, key=lambda p: p.area)
111
114
 
112
115
  @property
113
116
  def smallest(self) -> PhotoUrl:
114
- return min(self.urls, key=lambda p: p.height * p.width)
117
+ return min(self.root, key=lambda p: p.area)
118
+
119
+ @property
120
+ def urls(self):
121
+ return self.root
115
122
 
116
123
 
117
124
  class FeedVideo(BaseModel):
@@ -1,9 +1,12 @@
1
1
  import typing as t
2
2
  from base64 import b64encode
3
+ from io import BytesIO
3
4
  from math import floor
5
+ from os import PathLike
4
6
  from time import time
5
7
 
6
- from pydantic import BaseModel, Field, field_serializer
8
+ from pydantic import BaseModel, Field, field_serializer, field_validator
9
+ from typing_extensions import Buffer
7
10
 
8
11
  from aioqzone.utils.time import time_ms
9
12
 
@@ -183,6 +186,22 @@ class UploadPicParams(QzoneRequestParams):
183
186
  def b64_picture(self, picture: t.ByteString) -> str:
184
187
  return b64encode(picture).decode()
185
188
 
189
+ @classmethod
190
+ def from_image(cls, image_file: t.Union[str, PathLike, t.IO[bytes]], quality=70):
191
+ import PIL.Image as image
192
+
193
+ with image.open(image_file) as f:
194
+ buf = BytesIO()
195
+ f.save(buf, "JPEG", quality=quality)
196
+ return cls(
197
+ picture=buf.getvalue(), hd_height=f.height, hd_width=f.width, hd_quality=quality
198
+ )
199
+
200
+ @classmethod
201
+ def from_bytes(cls, image_bytes: Buffer, quality=70):
202
+ buf = BytesIO(image_bytes)
203
+ return cls.from_image(buf, quality=quality)
204
+
186
205
 
187
206
  class PhotosPreuploadParams(QzoneRequestParams):
188
207
  uin_fields = ("uin",)
@@ -33,6 +33,7 @@ __all__ = [
33
33
  "UploadPicResponse",
34
34
  "PhotosPreuploadResponse",
35
35
  "FeedData",
36
+ "PicInfo",
36
37
  "ProfileFeedData",
37
38
  ]
38
39
 
@@ -273,4 +274,4 @@ class PhotosPreuploadResponse(QzoneResponse):
273
274
 
274
275
  picinfos = json_loads(m.group(1))
275
276
  assert isinstance(picinfos, list)
276
- return dict(photos=[PicInfo.from_response_object(info["picinfo"]) for info in picinfos])
277
+ return dict(photos=[PicInfo.from_response_object(info["picinfo"]) for info in picinfos]) # type: ignore
File without changes
File without changes