aioqzone 0.13.0.dev1__tar.gz → 0.13.0.dev2__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 (56) hide show
  1. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/PKG-INFO +1 -1
  2. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/pyproject.toml +8 -9
  3. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/raw.py +6 -4
  4. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/raw.py +29 -17
  5. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/html.py +8 -7
  6. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/qr/__init__.py +1 -2
  7. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/__init__.py +3 -5
  8. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/encrypt.py +2 -3
  9. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/h5.py +2 -2
  10. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/web.py +2 -2
  11. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/utils/iter.py +5 -1
  12. aioqzone-0.13.0.dev1/src/qqqr/utils/daug.py +0 -25
  13. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/LICENSE +0 -0
  14. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/README.md +0 -0
  15. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/__init__.py +0 -0
  16. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/__init__.py +0 -0
  17. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/__init__.py +0 -0
  18. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/h5/model.py +0 -0
  19. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/__init__.py +0 -0
  20. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/loginman/_base.py +0 -0
  21. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/__init__.py +0 -0
  22. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/constant.py +0 -0
  23. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/api/web/model.py +0 -0
  24. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/event/__init__.py +0 -0
  25. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/event/login.py +0 -0
  26. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/exception.py +0 -0
  27. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/py.typed +0 -0
  28. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/__init__.py +0 -0
  29. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/entity.py +0 -0
  30. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/internal.py +0 -0
  31. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/__init__.py +0 -0
  32. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/h5.py +0 -0
  33. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/type/resp/web.py +0 -0
  34. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/__init__.py +0 -0
  35. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/catch.py +0 -0
  36. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/entity.py +0 -0
  37. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/regex.py +0 -0
  38. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/aioqzone/utils/time.py +0 -0
  39. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/__init__.py +0 -0
  40. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/base.py +0 -0
  41. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/constant.py +0 -0
  42. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/event/__init__.py +0 -0
  43. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/event/evt.py +0 -0
  44. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/event/evtmgr.py +0 -0
  45. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/event/login.py +0 -0
  46. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/exception.py +0 -0
  47. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/py.typed +0 -0
  48. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/qr/type.py +0 -0
  49. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/ssl.py +0 -0
  50. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/type.py +0 -0
  51. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/__init__.py +0 -0
  52. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/captcha/jigsaw.py +0 -0
  53. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/up/type.py +0 -0
  54. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/utils/encrypt.py +0 -0
  55. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/utils/jsjson.py +0 -0
  56. {aioqzone-0.13.0.dev1 → aioqzone-0.13.0.dev2}/src/qqqr/utils/net.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: aioqzone
3
- Version: 0.13.0.dev1
3
+ Version: 0.13.0.dev2
4
4
  Summary: Python wrapper for Qzone login and Qzone HTTP APIs.
5
5
  Home-page: https://github.com/aioqzone/aioqzone
6
6
  License: AGPL-3.0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "aioqzone"
3
- version = "0.13.0.dev1"
3
+ version = "0.13.0.dev2"
4
4
  description = "Python wrapper for Qzone login and Qzone HTTP APIs."
5
5
  authors = ["aioqzone <zzzzss990315@gmail.com>"]
6
6
  license = "AGPL-3.0"
@@ -33,10 +33,10 @@ pydantic = "^1.10.4"
33
33
  rsa = "^4.8"
34
34
  lxml = "^4.9.1"
35
35
  cssselect = "^1.1.0"
36
- exceptiongroup = { version = "^1.1.1", python = "<3.11", source = "PyPI" }
36
+ exceptiongroup = { version = "^1.1.1", python = "<3.11"}
37
37
 
38
- numpy = { version = "^1.22.3", optional = true, source = "PyPI" }
39
- pillow = { version = "^9.4.0", optional = true, source = "PyPI" }
38
+ numpy = { version = "^1.22.3", optional = true}
39
+ pillow = { version = "^9.4.0", optional = true}
40
40
  pychaosvm = { version = "^0.2.3", optional = true, source = "aioqzone-index" }
41
41
 
42
42
  [tool.poetry.extras]
@@ -47,7 +47,7 @@ captcha = ["numpy", "pillow", "pychaosvm"]
47
47
  optional = false
48
48
 
49
49
  [tool.poetry.group.test.dependencies]
50
- pytest = "^7.2.1"
50
+ pytest = "^7.4.0"
51
51
  pytest-asyncio = "~0.21.0"
52
52
 
53
53
  [tool.poetry.group.dev]
@@ -62,17 +62,16 @@ isort = "*"
62
62
  optional = true
63
63
 
64
64
  [tool.poetry.group.docs.dependencies]
65
- Sphinx = "^6.1.3"
65
+ Sphinx = "^7.0.1"
66
66
  autodoc-pydantic = "*"
67
67
  sphinx-autodoc-typehints = "^1.19.5"
68
- sphinx-rtd-theme = "*"
68
+ furo = "*"
69
69
  sphinx-intl = "*"
70
70
 
71
71
  [[tool.poetry.source]]
72
72
  name = "aioqzone-index"
73
73
  url = "https://aioqzone.github.io/aioqzone-index/simple"
74
- priority = "primary"
75
-
74
+ priority = "supplemental"
76
75
 
77
76
  [[tool.poetry.source]]
78
77
  name = "PyPI"
@@ -9,6 +9,7 @@ from aioqzone.api.loginman import Loginable
9
9
  from aioqzone.exception import QzoneError
10
10
  from aioqzone.utils.catch import HTTPStatusErrorDispatch, QzoneErrorDispatch
11
11
  from aioqzone.utils.regex import entire_closing, response_callback
12
+ from qqqr.utils.iter import firstn
12
13
  from qqqr.utils.jsjson import JsonValue, json_loads
13
14
  from qqqr.utils.net import ClientAdapter
14
15
 
@@ -102,7 +103,8 @@ class QzoneH5RawAPI:
102
103
  """
103
104
 
104
105
  with QzoneErrorDispatch() as qze, HTTPStatusErrorDispatch() as hse:
105
- qze.dispatch(-3000, suppress=lambda e: "登录" in e.msg) # -3000: 请先登录
106
+ # NOTE: 尽管只有“-3000: 请先登录”明确要求重新登录,但似乎任何原因的-3000错误值都意味着cookie过期。因此移除了对message的校验
107
+ qze.dispatch(-3000)
106
108
  qze.dispatch(-10000)
107
109
  hse.dispatch(302, 403)
108
110
  return await func(*args, **kwds)
@@ -150,13 +152,13 @@ class QzoneH5RawAPI:
150
152
 
151
153
  assert isinstance(r, dict)
152
154
 
153
- err = next(filter(lambda i: i is not None, (r.get(i) for i in errno_key)), None)
155
+ err = firstn((r.get(i) for i in errno_key), lambda i: i is not None)
154
156
  assert err is not None, f"no {errno_key} in {r.keys()}"
155
157
  assert isinstance(err, (int, str))
156
158
  err = int(err)
157
159
 
158
160
  if err != 0:
159
- msg = next(filter(None, (r.get(i) for i in msg_key)), None)
161
+ msg = firstn((r.get(i) for i in msg_key), lambda i: i is not None)
160
162
  if msg:
161
163
  raise QzoneError(err, msg, rdict=r)
162
164
  else:
@@ -183,7 +185,7 @@ class QzoneH5RawAPI:
183
185
  raise RuntimeError("script tag not found")
184
186
 
185
187
  texts: List[str] = [s.text for s in scripts]
186
- script = next(filter(lambda s: "shine0callback" in s, texts), None)
188
+ script = firstn(texts, lambda s: "shine0callback" in s)
187
189
  if not script:
188
190
  raise RuntimeError("data script not found")
189
191
 
@@ -17,7 +17,7 @@ from aioqzone.type.internal import AlbumData, LikeData
17
17
  from aioqzone.utils.catch import HTTPStatusErrorDispatch, QzoneErrorDispatch
18
18
  from aioqzone.utils.regex import response_callback
19
19
  from aioqzone.utils.time import time_ms
20
- from qqqr.utils.daug import du
20
+ from qqqr.utils.iter import firstn
21
21
  from qqqr.utils.jsjson import JsonValue, json_loads
22
22
  from qqqr.utils.net import ClientAdapter, raise_for_status
23
23
 
@@ -156,13 +156,13 @@ class QzoneWebRawAPI:
156
156
  r = json_loads(rtext)
157
157
  assert isinstance(r, dict)
158
158
 
159
- err = next(filter(lambda i: i is not None, (r.get(i) for i in errno_key)), None)
159
+ err = firstn((r.get(i) for i in errno_key), lambda i: i is not None)
160
160
  assert err is not None, f"no {errno_key} in {r.keys()}"
161
161
  assert isinstance(err, (int, str))
162
162
  err = int(err)
163
163
 
164
164
  if err != 0:
165
- msg = next(filter(None, (r.get(i) for i in msg_key)), None)
165
+ msg = firstn((r.get(i) for i in msg_key), lambda i: i is not None)
166
166
  if msg:
167
167
  raise QzoneError(err, msg, rdict=r)
168
168
  else:
@@ -238,10 +238,11 @@ class QzoneWebRawAPI:
238
238
  "usertime": time_ms(),
239
239
  "externparam": external,
240
240
  }
241
+ query.update(default)
241
242
 
242
243
  @self._relogin_retry
243
244
  async def retry_closure():
244
- async with self.host_get(const.feeds3_html_more, du(default, query)) as r:
245
+ async with self.host_get(const.feeds3_html_more, query) as r:
245
246
  r.raise_for_status()
246
247
  rtext = r.text
247
248
 
@@ -288,11 +289,12 @@ class QzoneWebRawAPI:
288
289
  "tid": tid,
289
290
  "feedsType": feedstype,
290
291
  }
291
- logger.debug("emotion_getcomments post data:", body)
292
+ logger.debug(f"emotion_getcomments post data: {body}")
293
+ body.update(default)
292
294
 
293
295
  @self._relogin_retry
294
296
  async def retry_closure():
295
- async with self.host_post(const.emotion_getcomments, data=du(default, body)) as r:
297
+ async with self.host_post(const.emotion_getcomments, data=body) as r:
296
298
  r.raise_for_status()
297
299
  rtext = r.text
298
300
 
@@ -326,10 +328,11 @@ class QzoneWebRawAPI:
326
328
  "need_private_comment": 1,
327
329
  }
328
330
  query = {"uin": owner, "tid": fid}
331
+ query.update(default)
329
332
 
330
333
  @self._relogin_retry
331
334
  async def retry_closure():
332
- async with self.host_get(const.emotion_msgdetail, params=du(default, query)) as r:
335
+ async with self.host_get(const.emotion_msgdetail, params=query) as r:
333
336
  r.raise_for_status()
334
337
  return self._rtext_handler(r.text)
335
338
 
@@ -387,12 +390,14 @@ class QzoneWebRawAPI:
387
390
  "abstime": likedata.abstime,
388
391
  "fid": likedata.fid,
389
392
  }
390
- logger.debug("like_app post data:", body)
393
+ logger.debug(f"like_app post data: {body}")
394
+
395
+ body.update(default)
391
396
  url = const.internal_dolike_app if like else const.internal_unlike_app
392
397
 
393
398
  @self._relogin_retry
394
399
  async def retry_closure():
395
- async with self.host_post(url, data=du(default, body)) as r:
400
+ async with self.host_post(url, data=body) as r:
396
401
  r.raise_for_status()
397
402
  return self._rtext_handler(r.text, errno_key=("code", "ret"))
398
403
 
@@ -454,10 +459,11 @@ class QzoneWebRawAPI:
454
459
  "t": randint(int(1e8), int(1e9 - 1))
455
460
  # The distribution is not consistent with photo.js; but the format is.
456
461
  }
462
+ query.update(default)
457
463
 
458
464
  @self._relogin_retry
459
465
  async def retry_closure():
460
- async with self.host_get(const.floatview_photo_list, du(default, query)) as r:
466
+ async with self.host_get(const.floatview_photo_list, query) as r:
461
467
  r.raise_for_status()
462
468
  return self._rtext_handler(r.text)
463
469
 
@@ -506,9 +512,12 @@ class QzoneWebRawAPI:
506
512
  "need_private_comment": 1,
507
513
  }
508
514
 
515
+ if pos:
516
+ param.update(add)
517
+
509
518
  @self._relogin_retry
510
519
  async def retry_closure():
511
- async with self.host_get(const.emotion_msglist, du(param, add) if pos else param) as r:
520
+ async with self.host_get(const.emotion_msglist, param) as r:
512
521
  r.raise_for_status()
513
522
  rtext = r.text
514
523
  return self._rtext_handler(rtext)
@@ -551,11 +560,12 @@ class QzoneWebRawAPI:
551
560
  "feedversion": 1,
552
561
  "hostuin": self.login.uin,
553
562
  }
554
- logger.debug("emotion_publish post data:", body)
563
+ logger.debug(f"emotion_publish post data: {body}")
564
+ body.update(default)
555
565
 
556
566
  @self._relogin_retry
557
567
  async def retry_closure():
558
- async with self.host_post(const.emotion_publish, data=du(default, body)) as r:
568
+ async with self.host_post(const.emotion_publish, data=body) as r:
559
569
  r.raise_for_status()
560
570
  return self._rtext_handler(r.text)
561
571
 
@@ -649,11 +659,12 @@ class QzoneWebRawAPI:
649
659
  "hostuin": uin or self.login.uin,
650
660
  # 'pic_bo': ''
651
661
  }
652
- logger.debug("emotion_update post data:", body)
662
+ logger.debug(f"emotion_update post data: {body}")
663
+ body.update(default)
653
664
 
654
665
  @self._relogin_retry
655
666
  async def retry_closure():
656
- async with self.host_post(const.emotion_update, data=du(default, body)) as r:
667
+ async with self.host_post(const.emotion_update, data=body) as r:
657
668
  r.raise_for_status()
658
669
  return self._rtext_handler(r.text)
659
670
 
@@ -706,11 +717,12 @@ class QzoneWebRawAPI:
706
717
  private=int(is_private),
707
718
  paramstr=1,
708
719
  )
709
- logger.debug("emotion_re_feeds post data:", data)
720
+ logger.debug(f"emotion_re_feeds post data: {data}")
721
+ data.update(default)
710
722
 
711
723
  @self._relogin_retry
712
724
  async def retry_closure():
713
- async with self.host_post(const.emotion_re_feeds, data=du(default, data)) as r:
725
+ async with self.host_post(const.emotion_re_feeds, data=data) as r:
714
726
  r.raise_for_status()
715
727
  return self._rtext_handler(r.text)
716
728
 
@@ -7,12 +7,13 @@ Use this module to get some data from Qzone html feed.
7
7
  """
8
8
  import logging
9
9
  import re
10
+ from contextlib import suppress
10
11
  from typing import Iterable, List, Optional, Union, cast
11
12
 
12
13
  from lxml.html import HtmlElement, fromstring
13
- from pydantic import BaseModel, HttpUrl
14
+ from pydantic import BaseModel, HttpUrl, ValidationError
14
15
 
15
- from qqqr.utils.daug import di
16
+ from qqqr.utils.iter import firstn
16
17
 
17
18
  from ..type.entity import ConEntity
18
19
  from ..type.internal import AlbumData
@@ -81,7 +82,7 @@ class HtmlContent(BaseModel):
81
82
  img_data = lambda a: {k[5:]: v for k, v in a.attrib.items() if k.startswith("data-")}
82
83
 
83
84
  def load_src(a: Iterable[HtmlElement]) -> Optional[HttpUrl]:
84
- o: Optional[HtmlElement] = next(filter(lambda i: i.tag == "img", a), None)
85
+ o = firstn(a, lambda i: i.tag == "img")
85
86
  if o is None:
86
87
  return
87
88
  src: str = o.get("src", "")
@@ -101,11 +102,11 @@ class HtmlContent(BaseModel):
101
102
 
102
103
  finfo: HtmlElement = mxsafe(root.cssselect("div.f-info"))
103
104
  lia: List[HtmlElement] = root.cssselect("div.f-ct a.img-item")
105
+ (d := img_data(lia[0]))["hostuin"] = hostuin
104
106
 
105
- try:
106
- album = AlbumData.parse_obj(di(img_data(lia[0]), hostuin=hostuin))
107
- except:
108
- album = None
107
+ album = None
108
+ with suppress(ValidationError):
109
+ album = AlbumData.parse_obj(d)
109
110
 
110
111
  pic = [
111
112
  PicRep(
@@ -11,7 +11,6 @@ from ..constant import StatusCode
11
11
  from ..event import Emittable, hook_guard
12
12
  from ..event.login import QrEvent
13
13
  from ..exception import UserBreak
14
- from ..utils.daug import du
15
14
  from ..utils.encrypt import hash33
16
15
 
17
16
  log = logging.getLogger(__name__)
@@ -86,7 +85,7 @@ class QrLogin(LoginBase[QrSession], Emittable[QrEvent]):
86
85
  "daid": self.app.daid,
87
86
  }
88
87
 
89
- async with self.client.get(POLL_QR, params=du(data, const)) as r:
88
+ async with self.client.get(POLL_QR, params=data.update(const) or data) as r:
90
89
  r.raise_for_status()
91
90
  rl = re.findall(r"'(.*?)'[,\)]", r.text)
92
91
 
@@ -3,6 +3,7 @@ import base64
3
3
  import json
4
4
  import logging
5
5
  import re
6
+ from contextlib import suppress
6
7
  from hashlib import md5
7
8
  from ipaddress import IPv4Address
8
9
  from random import random
@@ -13,7 +14,6 @@ from chaosvm import prepare
13
14
  from chaosvm.proxy.dom import TDC
14
15
  from httpx import URL
15
16
 
16
- from ...utils.daug import du
17
17
  from ...utils.iter import first
18
18
  from ...utils.net import ClientAdapter
19
19
  from ..type import PrehandleResp, VerifyResp
@@ -165,7 +165,7 @@ class Captcha:
165
165
  "subsid": 1,
166
166
  "callback": CALLBACK,
167
167
  }
168
- async with self.client.get(PREHANDLE_URL, params=du(const, data)) as r:
168
+ async with self.client.get(PREHANDLE_URL, params=data.update(const) or data) as r:
169
169
  r.raise_for_status()
170
170
  m = re.search(CALLBACK + r"\((\{.*\})\)", r.text)
171
171
 
@@ -189,11 +189,9 @@ class Captcha:
189
189
  # BUG: should always bypass client's proxy settings
190
190
  async with self.client.get("https://" + api) as r:
191
191
  cand = r.text.strip()
192
- try:
192
+ with suppress(ValueError):
193
193
  IPv4Address(cand)
194
194
  return cand
195
- except ValueError:
196
- continue
197
195
  return ""
198
196
 
199
197
  async def get_captcha_problem(self, sess: TcaptchaSession):
@@ -4,6 +4,7 @@ import base64
4
4
  import struct
5
5
  from abc import ABC, abstractmethod
6
6
  from binascii import hexlify
7
+ from contextlib import suppress
7
8
  from hashlib import md5
8
9
  from random import randint
9
10
  from typing import Union
@@ -121,11 +122,9 @@ class TeaEncoder(PasswdEncoder):
121
122
  """
122
123
  e = []
123
124
  for i in range(0, len(s), 2):
124
- try:
125
+ with suppress(ValueError):
125
126
  e.append(int(s[i : i + 2], 16))
126
127
  continue
127
- except ValueError:
128
- pass
129
128
  try:
130
129
  e.append(int(s[i : i + 1], 16))
131
130
  except ValueError:
@@ -4,7 +4,6 @@ import re
4
4
  from httpx import URL
5
5
 
6
6
  from qqqr.constant import StatusCode
7
- from qqqr.utils.daug import du
8
7
 
9
8
  from .type import CheckResp, LoginResp
10
9
  from .web import UpWebLogin, UpWebSession
@@ -93,7 +92,8 @@ class UpH5Login(UpWebLogin):
93
92
  data["pt_sms_code"] = sess.sms_code
94
93
  self.referer = "https://xui.ptlogin2.qq.com/"
95
94
 
96
- async with self.client.get(LOGIN_URL, params=du(data, const)) as response:
95
+ data.update(const)
96
+ async with self.client.get(LOGIN_URL, params=data) as response:
97
97
  response.raise_for_status()
98
98
 
99
99
  rl = re.findall(r"'(.*?)'[,\)]", response.text)
@@ -13,7 +13,6 @@ from qqqr.event import Emittable
13
13
  from qqqr.event.login import UpEvent
14
14
  from qqqr.exception import TencentLoginError
15
15
  from qqqr.type import APPID, PT_QR_APP, Proxy
16
- from qqqr.utils.daug import du
17
16
  from qqqr.utils.net import ClientAdapter
18
17
 
19
18
  from .encrypt import PasswdEncoder, TeaEncoder
@@ -230,7 +229,8 @@ class UpWebLogin(LoginBase[UpWebSession], Emittable[UpEvent]):
230
229
  data["pt_sms_code"] = sess.sms_code
231
230
  self.referer = sess.login_referer
232
231
 
233
- async with self.client.get(LOGIN_URL, params=du(data, const)) as response:
232
+ data.update(const)
233
+ async with self.client.get(LOGIN_URL, params=data) as response:
234
234
  response.raise_for_status()
235
235
 
236
236
  rl = re.findall(r"'(.*?)'[,\)]", response.text)
@@ -20,6 +20,10 @@ def first(
20
20
  it: Iterable[T], pred: Optional[Callable[[T], Union[object, None]]] = None, *, default: D = ...
21
21
  ) -> Union[T, D]:
22
22
  f = filter(pred, it)
23
- if default == ...:
23
+ if default is ...:
24
24
  return next(f)
25
25
  return next(f, default)
26
+
27
+
28
+ def firstn(it: Iterable[T], pred: Optional[Callable[[T], Union[object, None]]] = None):
29
+ return first(it, pred, default=None)
@@ -1,25 +0,0 @@
1
- """Augment python `dict` since we cannot use py37+ features...
2
-
3
- Maybe these are user-defined syntax-sugars?
4
- """
5
-
6
- from itertools import chain
7
- from typing import Dict, Hashable, Iterable, Mapping, MutableMapping, Tuple, TypeVar
8
-
9
- K = TypeVar("K", bound=Hashable)
10
- V = TypeVar("V")
11
-
12
-
13
- def u_(*dicts: Mapping[K, V]) -> Iterable[Tuple[K, V]]:
14
- return chain(*(i.items() for i in dicts))
15
-
16
-
17
- def du(*dicts: Mapping[K, V]) -> Dict[K, V]:
18
- """returns union of these dict."""
19
- return dict(u_(*dicts))
20
-
21
-
22
- def di(d: MutableMapping[K, V], **kw) -> MutableMapping[K, V]:
23
- """insert kw into this dict."""
24
- d.update(**kw)
25
- return d
File without changes
File without changes