sticker-convert 2.13.3.0__py3-none-any.whl → 2.17.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.
Files changed (93) hide show
  1. sticker_convert/__main__.py +24 -27
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +149 -118
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -330
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +281 -263
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +139 -135
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +250 -235
  12. sticker_convert/{utils/auth → auth}/telegram_api.py +736 -675
  13. sticker_convert/cli.py +623 -608
  14. sticker_convert/converter.py +1093 -1084
  15. sticker_convert/definitions.py +4 -0
  16. sticker_convert/downloaders/download_band.py +111 -110
  17. sticker_convert/downloaders/download_base.py +171 -166
  18. sticker_convert/downloaders/download_discord.py +92 -91
  19. sticker_convert/downloaders/download_kakao.py +417 -404
  20. sticker_convert/downloaders/download_line.py +484 -475
  21. sticker_convert/downloaders/download_ogq.py +80 -79
  22. sticker_convert/downloaders/download_signal.py +108 -105
  23. sticker_convert/downloaders/download_telegram.py +56 -55
  24. sticker_convert/downloaders/download_viber.py +121 -120
  25. sticker_convert/gui.py +788 -873
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -166
  27. sticker_convert/gui_components/frames/config_frame.py +156 -113
  28. sticker_convert/gui_components/frames/control_frame.py +32 -30
  29. sticker_convert/gui_components/frames/cred_frame.py +232 -233
  30. sticker_convert/gui_components/frames/input_frame.py +139 -137
  31. sticker_convert/gui_components/frames/output_frame.py +112 -110
  32. sticker_convert/gui_components/frames/right_clicker.py +25 -23
  33. sticker_convert/gui_components/windows/advanced_compression_window.py +757 -757
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -82
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -321
  37. sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
  38. sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -89
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -168
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/README.md +10 -10
  42. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  43. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  50. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  51. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  52. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  53. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  54. sticker_convert/job.py +166 -130
  55. sticker_convert/job_option.py +1 -0
  56. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  57. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  58. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  59. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  60. sticker_convert/py.typed +0 -0
  61. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  62. sticker_convert/resources/help.ja_JP.json +88 -0
  63. sticker_convert/resources/help.json +10 -7
  64. sticker_convert/resources/help.zh_CN.json +88 -0
  65. sticker_convert/resources/help.zh_TW.json +88 -0
  66. sticker_convert/resources/input.ja_JP.json +74 -0
  67. sticker_convert/resources/input.json +121 -121
  68. sticker_convert/resources/input.zh_CN.json +74 -0
  69. sticker_convert/resources/input.zh_TW.json +74 -0
  70. sticker_convert/resources/output.ja_JP.json +38 -0
  71. sticker_convert/resources/output.zh_CN.json +38 -0
  72. sticker_convert/resources/output.zh_TW.json +38 -0
  73. sticker_convert/uploaders/compress_wastickers.py +186 -177
  74. sticker_convert/uploaders/upload_base.py +44 -35
  75. sticker_convert/uploaders/upload_signal.py +218 -203
  76. sticker_convert/uploaders/upload_telegram.py +353 -338
  77. sticker_convert/uploaders/upload_viber.py +178 -169
  78. sticker_convert/uploaders/xcode_imessage.py +295 -286
  79. sticker_convert/utils/callback.py +238 -6
  80. sticker_convert/utils/emoji.py +16 -4
  81. sticker_convert/utils/files/json_resources_loader.py +24 -19
  82. sticker_convert/utils/files/metadata_handler.py +3 -3
  83. sticker_convert/utils/translate.py +108 -0
  84. sticker_convert/utils/url_detect.py +40 -37
  85. sticker_convert/version.py +1 -1
  86. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +89 -74
  87. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/RECORD +91 -74
  88. sticker_convert/utils/auth/telethon_setup.py +0 -97
  89. sticker_convert/utils/singletons.py +0 -18
  90. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +0 -0
  91. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  92. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/licenses/LICENSE +0 -0
  93. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ import os
3
3
  import platform
4
4
  import sys
5
5
  from pathlib import Path
6
+ from typing import Any, Dict
6
7
 
7
8
 
8
9
  def get_root_dir() -> Path:
@@ -89,3 +90,6 @@ SVG_SAMPLE_FPS = 30
89
90
  # If width and height not set in SVG tag, import at this dimension
90
91
  SVG_DEFAULT_WIDTH = 1024
91
92
  SVG_DEFAULT_HEIGHT = 1024
93
+
94
+ # A shared dictionary
95
+ RUNTIME_STATE: Dict[str, Any] = {}
@@ -1,110 +1,111 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import json
5
- import zipfile
6
- from io import BytesIO
7
- from pathlib import Path
8
- from typing import Any, Dict, Optional, Tuple
9
- from urllib.parse import urlparse
10
-
11
- import requests
12
-
13
- from sticker_convert.downloaders.download_base import DownloadBase
14
- from sticker_convert.job_option import CredOption, InputOption
15
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
16
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
17
-
18
-
19
- class DownloadBand(DownloadBase):
20
- def __init__(self, *args: Any, **kwargs: Any) -> None:
21
- super().__init__(*args, **kwargs)
22
-
23
- def decompress(self, zip_file: bytes) -> int:
24
- with zipfile.ZipFile(BytesIO(zip_file)) as zf:
25
- self.cb.put("Unzipping...")
26
-
27
- zf_files = zf.namelist()
28
- animated = [i for i in zf_files if "animation/" in i]
29
- if len(animated) > 0:
30
- pack_files = animated
31
- else:
32
- pack_files = [
33
- i
34
- for i in zf_files
35
- if i.endswith(".meta") is False and "_key" not in i
36
- ]
37
-
38
- self.cb.put(
39
- (
40
- "bar",
41
- None,
42
- {"set_progress_mode": "determinate", "steps": len(pack_files)},
43
- )
44
- )
45
-
46
- for num, sticker in enumerate(pack_files):
47
- data = zf.read(sticker)
48
- self.cb.put(f"Read {sticker}")
49
- ext = sticker.split(".")[-1]
50
-
51
- out_path = Path(self.out_dir, str(num).zfill(3) + f".{ext}")
52
- with open(out_path, "wb") as f:
53
- f.write(data)
54
-
55
- self.cb.put("update_bar")
56
-
57
- return len(pack_files)
58
-
59
- def get_metadata(self, pack_no: str) -> Optional[Dict[Any, Any]]:
60
- r = requests.get(
61
- f"https://sapi.band.us/v1.0.0/get_sticker_info?pack_no={pack_no}"
62
- )
63
- if r.text:
64
- return json.loads(r.text)
65
- else:
66
- return None
67
-
68
- def download_stickers_band(self) -> Tuple[int, int]:
69
- if urlparse(self.url).netloc != "www.band.us" and self.url.isnumeric() is False:
70
- self.cb.put("Download failed: Unsupported URL format")
71
- return 0, 0
72
-
73
- if self.url.isnumeric():
74
- pack_no = self.url
75
- else:
76
- pack_no = urlparse(self.url).path.split("/")[-1]
77
- metadata = self.get_metadata(pack_no)
78
- if metadata:
79
- self.title = metadata["result_data"]["sticker"]["name"]
80
- else:
81
- self.cb.put("Download failed: Failed to get metadata")
82
- return 0, 0
83
-
84
- MetadataHandler.set_metadata(self.out_dir, title=self.title)
85
-
86
- pack_url = f"http://s.cmstatic.net/band/sticker/v2/{pack_no}/shop/pack"
87
- zip_file = self.download_file(pack_url)
88
-
89
- if zip_file:
90
- self.cb.put(f"Downloaded {pack_url}")
91
- else:
92
- self.cb.put(f"Cannot download {pack_url}")
93
- return 0, 0
94
-
95
- pack_files_no = self.decompress(zip_file)
96
-
97
- cover_url = f"http://s.cmstatic.net/band/sticker/v2/{pack_no}/shop/main"
98
- self.download_file(cover_url, self.out_dir / "cover.png")
99
-
100
- return pack_files_no, pack_files_no
101
-
102
- @staticmethod
103
- def start(
104
- opt_input: InputOption,
105
- opt_cred: Optional[CredOption],
106
- cb: CallbackProtocol,
107
- cb_return: CallbackReturn,
108
- ) -> Tuple[int, int]:
109
- downloader = DownloadBand(opt_input, opt_cred, cb, cb_return)
110
- return downloader.download_stickers_band()
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import zipfile
6
+ from io import BytesIO
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional, Tuple
9
+ from urllib.parse import urlparse
10
+
11
+ import requests
12
+
13
+ from sticker_convert.downloaders.download_base import DownloadBase
14
+ from sticker_convert.job_option import CredOption, InputOption
15
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
16
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
17
+ from sticker_convert.utils.translate import I
18
+
19
+
20
+ class DownloadBand(DownloadBase):
21
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
22
+ super().__init__(*args, **kwargs)
23
+
24
+ def decompress(self, zip_file: bytes) -> int:
25
+ with zipfile.ZipFile(BytesIO(zip_file)) as zf:
26
+ self.cb.put(I("Unzipping..."))
27
+
28
+ zf_files = zf.namelist()
29
+ animated = [i for i in zf_files if "animation/" in i]
30
+ if len(animated) > 0:
31
+ pack_files = animated
32
+ else:
33
+ pack_files = [
34
+ i
35
+ for i in zf_files
36
+ if i.endswith(".meta") is False and "_key" not in i
37
+ ]
38
+
39
+ self.cb.put(
40
+ (
41
+ "bar",
42
+ None,
43
+ {"set_progress_mode": "determinate", "steps": len(pack_files)},
44
+ )
45
+ )
46
+
47
+ for num, sticker in enumerate(pack_files):
48
+ data = zf.read(sticker)
49
+ self.cb.put(f"Read {sticker}")
50
+ ext = sticker.split(".")[-1]
51
+
52
+ out_path = Path(self.out_dir, str(num).zfill(3) + f".{ext}")
53
+ with open(out_path, "wb") as f:
54
+ f.write(data)
55
+
56
+ self.cb.put("update_bar")
57
+
58
+ return len(pack_files)
59
+
60
+ def get_metadata(self, pack_no: str) -> Optional[Dict[Any, Any]]:
61
+ r = requests.get(
62
+ f"https://sapi.band.us/v1.0.0/get_sticker_info?pack_no={pack_no}"
63
+ )
64
+ if r.text:
65
+ return json.loads(r.text)
66
+ else:
67
+ return None
68
+
69
+ def download_stickers_band(self) -> Tuple[int, int]:
70
+ if urlparse(self.url).netloc != "www.band.us" and self.url.isnumeric() is False:
71
+ self.cb.put(I("Download failed: Unsupported URL format"))
72
+ return 0, 0
73
+
74
+ if self.url.isnumeric():
75
+ pack_no = self.url
76
+ else:
77
+ pack_no = urlparse(self.url).path.split("/")[-1]
78
+ metadata = self.get_metadata(pack_no)
79
+ if metadata:
80
+ self.title = metadata["result_data"]["sticker"]["name"]
81
+ else:
82
+ self.cb.put(I("Download failed: Failed to get metadata"))
83
+ return 0, 0
84
+
85
+ MetadataHandler.set_metadata(self.out_dir, title=self.title)
86
+
87
+ pack_url = f"http://s.cmstatic.net/band/sticker/v2/{pack_no}/shop/pack"
88
+ zip_file = self.download_file(pack_url)
89
+
90
+ if zip_file:
91
+ self.cb.put(I("Downloaded {}").format(pack_url))
92
+ else:
93
+ self.cb.put(I("Cannot download {}").format(pack_url))
94
+ return 0, 0
95
+
96
+ pack_files_no = self.decompress(zip_file)
97
+
98
+ cover_url = f"http://s.cmstatic.net/band/sticker/v2/{pack_no}/shop/main"
99
+ self.download_file(cover_url, self.out_dir / "cover.png")
100
+
101
+ return pack_files_no, pack_files_no
102
+
103
+ @staticmethod
104
+ def start(
105
+ opt_input: InputOption,
106
+ opt_cred: Optional[CredOption],
107
+ cb: CallbackProtocol,
108
+ cb_return: CallbackReturn,
109
+ ) -> Tuple[int, int]:
110
+ downloader = DownloadBand(opt_input, opt_cred, cb, cb_return)
111
+ return downloader.download_stickers_band()
@@ -1,166 +1,171 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- from functools import partial
5
- from pathlib import Path
6
- from typing import Any, Dict, List, Optional, Tuple
7
-
8
- import anyio
9
- import httpx
10
- import requests
11
-
12
- from sticker_convert.job_option import CredOption, InputOption
13
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
-
15
-
16
- class DownloadBase:
17
- def __init__(
18
- self,
19
- opt_input: InputOption,
20
- opt_cred: Optional[CredOption],
21
- cb: CallbackProtocol,
22
- cb_return: CallbackReturn,
23
- ) -> None:
24
- self.url = opt_input.url
25
- self.out_dir = opt_input.dir
26
- self.input_option = opt_input.option
27
- self.opt_cred = opt_cred
28
- self.cb = cb
29
- self.cb_return = cb_return
30
-
31
- def download_multiple_files(
32
- self,
33
- targets: List[Tuple[str, Path]],
34
- retries: int = 3,
35
- headers: Optional[dict[Any, Any]] = None,
36
- **kwargs: Any,
37
- ) -> Dict[str, bool]:
38
- results: Dict[str, bool] = {}
39
- anyio.run(
40
- partial(
41
- self.download_multiple_files_async,
42
- targets,
43
- retries,
44
- headers,
45
- results,
46
- **kwargs,
47
- )
48
- )
49
- return results
50
-
51
- async def download_multiple_files_async(
52
- self,
53
- targets: List[Tuple[str, Path]],
54
- retries: int = 3,
55
- headers: Optional[dict[Any, Any]] = None,
56
- results: Optional[dict[str, bool]] = None,
57
- **kwargs: Any,
58
- ) -> None:
59
- # targets format: [(url1, dest2), (url2, dest2), ...]
60
- self.cb.put(
61
- ("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
62
- )
63
-
64
- semaphore = anyio.Semaphore(4)
65
-
66
- async with httpx.AsyncClient() as client:
67
- async with anyio.create_task_group() as tg:
68
- for url, dest in targets:
69
- tg.start_soon(
70
- self.download_file_async,
71
- semaphore,
72
- client,
73
- url,
74
- dest,
75
- retries,
76
- headers,
77
- results,
78
- **kwargs,
79
- )
80
-
81
- async def download_file_async(
82
- self,
83
- semaphore: anyio.Semaphore,
84
- client: httpx.AsyncClient,
85
- url: str,
86
- dest: Path,
87
- retries: int = 3,
88
- headers: Optional[dict[Any, Any]] = None,
89
- results: Optional[dict[str, bool]] = None,
90
- **kwargs: Any,
91
- ) -> None:
92
- async with semaphore:
93
- self.cb.put(f"Downloading {url}")
94
- success = False
95
- for retry in range(retries):
96
- response = await client.get(
97
- url, follow_redirects=True, headers=headers, **kwargs
98
- )
99
- success = response.is_success
100
-
101
- if success:
102
- async with await anyio.open_file(dest, "wb+") as f:
103
- await f.write(response.content)
104
- self.cb.put(f"Downloaded {url}")
105
- else:
106
- self.cb.put(
107
- f"Error {response.status_code}: {url} (tried {retry + 1}/{retries} times)"
108
- )
109
-
110
- if results is not None:
111
- results[url] = success
112
-
113
- self.cb.put("update_bar")
114
-
115
- def download_file(
116
- self,
117
- url: str,
118
- dest: Optional[Path] = None,
119
- retries: int = 3,
120
- show_progress: bool = True,
121
- **kwargs: Any,
122
- ) -> bytes:
123
- result = b""
124
- chunk_size = 102400
125
-
126
- for retry in range(retries):
127
- try:
128
- response = requests.get(
129
- url, stream=True, allow_redirects=True, **kwargs
130
- )
131
- if not response.ok:
132
- return b""
133
- total_length = int(response.headers.get("content-length")) # type: ignore
134
-
135
- self.cb.put(f"Downloading {url}")
136
-
137
- if show_progress:
138
- steps = (total_length / chunk_size) + 1
139
- self.cb.put(
140
- (
141
- "bar",
142
- None,
143
- {"set_progress_mode": "determinate", "steps": int(steps)},
144
- )
145
- )
146
-
147
- for chunk in response.iter_content(chunk_size=chunk_size):
148
- if chunk:
149
- result += chunk
150
- if show_progress:
151
- self.cb.put("update_bar")
152
-
153
- break
154
- except requests.exceptions.RequestException as e:
155
- self.cb.put(
156
- f"Cannot download {url} (tried {retry + 1}/{retries} times): {e}"
157
- )
158
-
159
- if not result:
160
- return b""
161
- if dest:
162
- with open(dest, "wb+") as f:
163
- f.write(result)
164
- self.cb.put(f"Downloaded {url}")
165
- return b""
166
- return result
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+
8
+ import anyio
9
+ import httpx
10
+ import requests
11
+
12
+ from sticker_convert.job_option import CredOption, InputOption
13
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
+ from sticker_convert.utils.translate import I
15
+
16
+
17
+ class DownloadBase:
18
+ def __init__(
19
+ self,
20
+ opt_input: InputOption,
21
+ opt_cred: Optional[CredOption],
22
+ cb: CallbackProtocol,
23
+ cb_return: CallbackReturn,
24
+ ) -> None:
25
+ self.url = opt_input.url
26
+ self.out_dir = opt_input.dir
27
+ self.input_option = opt_input.option
28
+ self.opt_cred = opt_cred
29
+ self.cb = cb
30
+ self.cb_return = cb_return
31
+
32
+ def download_multiple_files(
33
+ self,
34
+ targets: List[Tuple[str, Path]],
35
+ retries: int = 3,
36
+ headers: Optional[dict[Any, Any]] = None,
37
+ **kwargs: Any,
38
+ ) -> Dict[str, bool]:
39
+ results: Dict[str, bool] = {}
40
+ anyio.run(
41
+ partial(
42
+ self.download_multiple_files_async,
43
+ targets,
44
+ retries,
45
+ headers,
46
+ results,
47
+ **kwargs,
48
+ )
49
+ )
50
+ return results
51
+
52
+ async def download_multiple_files_async(
53
+ self,
54
+ targets: List[Tuple[str, Path]],
55
+ retries: int = 3,
56
+ headers: Optional[dict[Any, Any]] = None,
57
+ results: Optional[dict[str, bool]] = None,
58
+ **kwargs: Any,
59
+ ) -> None:
60
+ # targets format: [(url1, dest2), (url2, dest2), ...]
61
+ self.cb.put(
62
+ ("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
63
+ )
64
+
65
+ semaphore = anyio.Semaphore(4)
66
+
67
+ async with httpx.AsyncClient() as client:
68
+ async with anyio.create_task_group() as tg:
69
+ for url, dest in targets:
70
+ tg.start_soon(
71
+ self.download_file_async,
72
+ semaphore,
73
+ client,
74
+ url,
75
+ dest,
76
+ retries,
77
+ headers,
78
+ results,
79
+ **kwargs,
80
+ )
81
+
82
+ async def download_file_async(
83
+ self,
84
+ semaphore: anyio.Semaphore,
85
+ client: httpx.AsyncClient,
86
+ url: str,
87
+ dest: Path,
88
+ retries: int = 3,
89
+ headers: Optional[dict[Any, Any]] = None,
90
+ results: Optional[dict[str, bool]] = None,
91
+ **kwargs: Any,
92
+ ) -> None:
93
+ async with semaphore:
94
+ self.cb.put(I("Downloading {}").format(url))
95
+ success = False
96
+ for retry in range(retries):
97
+ response = await client.get(
98
+ url, follow_redirects=True, headers=headers, **kwargs
99
+ )
100
+ success = response.is_success
101
+
102
+ if success:
103
+ async with await anyio.open_file(dest, "wb+") as f:
104
+ await f.write(response.content)
105
+ self.cb.put(I("Downloaded {}").format(url))
106
+ else:
107
+ self.cb.put(
108
+ I("Error {}: {} (tried {}/{} times)").format(
109
+ response.status_code, url, retry + 1, retries
110
+ )
111
+ )
112
+
113
+ if results is not None:
114
+ results[url] = success
115
+
116
+ self.cb.put("update_bar")
117
+
118
+ def download_file(
119
+ self,
120
+ url: str,
121
+ dest: Optional[Path] = None,
122
+ retries: int = 3,
123
+ show_progress: bool = True,
124
+ **kwargs: Any,
125
+ ) -> bytes:
126
+ result = b""
127
+ chunk_size = 102400
128
+
129
+ for retry in range(retries):
130
+ try:
131
+ response = requests.get(
132
+ url, stream=True, allow_redirects=True, **kwargs
133
+ )
134
+ if not response.ok:
135
+ return b""
136
+ total_length = int(response.headers.get("content-length")) # type: ignore
137
+
138
+ self.cb.put(I("Downloading {}").format(url))
139
+
140
+ if show_progress:
141
+ steps = (total_length / chunk_size) + 1
142
+ self.cb.put(
143
+ (
144
+ "bar",
145
+ None,
146
+ {"set_progress_mode": "determinate", "steps": int(steps)},
147
+ )
148
+ )
149
+
150
+ for chunk in response.iter_content(chunk_size=chunk_size):
151
+ if chunk:
152
+ result += chunk
153
+ if show_progress:
154
+ self.cb.put("update_bar")
155
+
156
+ break
157
+ except requests.exceptions.RequestException as e:
158
+ self.cb.put(
159
+ I("Cannot download {} (tried {}/{} times): {}").format(
160
+ url, retry + 1, retries, e
161
+ )
162
+ )
163
+
164
+ if not result:
165
+ return b""
166
+ if dest:
167
+ with open(dest, "wb+") as f:
168
+ f.write(result)
169
+ self.cb.put(I("Downloaded {}").format(url))
170
+ return b""
171
+ return result