sticker-convert 2.8.12__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 (124) hide show
  1. sticker_convert/__main__.py +24 -24
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/auth/auth_discord.py +149 -0
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/auth/auth_signal.py +139 -0
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/auth/auth_viber.py +250 -0
  12. sticker_convert/auth/telegram_api.py +736 -0
  13. sticker_convert/cli.py +623 -509
  14. sticker_convert/converter.py +1093 -962
  15. sticker_convert/definitions.py +11 -0
  16. sticker_convert/downloaders/download_band.py +111 -0
  17. sticker_convert/downloaders/download_base.py +171 -130
  18. sticker_convert/downloaders/download_discord.py +92 -0
  19. sticker_convert/downloaders/download_kakao.py +417 -255
  20. sticker_convert/downloaders/download_line.py +484 -472
  21. sticker_convert/downloaders/download_ogq.py +80 -0
  22. sticker_convert/downloaders/download_signal.py +108 -92
  23. sticker_convert/downloaders/download_telegram.py +56 -130
  24. sticker_convert/downloaders/download_viber.py +121 -95
  25. sticker_convert/gui.py +788 -795
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -165
  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 -162
  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 -715
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
  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 -135
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  42. sticker_convert/ios-message-stickers-template/README.md +10 -10
  43. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  50. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  51. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  52. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  53. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  54. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  55. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  56. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  57. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  58. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  59. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  60. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  61. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  62. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  63. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  64. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  65. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  66. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  67. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  68. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  69. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  70. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  71. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  72. sticker_convert/job.py +279 -179
  73. sticker_convert/job_option.py +15 -2
  74. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  75. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  76. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  77. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  78. sticker_convert/py.typed +0 -0
  79. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  80. sticker_convert/resources/compression.json +220 -16
  81. sticker_convert/resources/emoji.json +527 -77
  82. sticker_convert/resources/help.ja_JP.json +88 -0
  83. sticker_convert/resources/help.json +24 -10
  84. sticker_convert/resources/help.zh_CN.json +88 -0
  85. sticker_convert/resources/help.zh_TW.json +88 -0
  86. sticker_convert/resources/input.ja_JP.json +74 -0
  87. sticker_convert/resources/input.json +121 -71
  88. sticker_convert/resources/input.zh_CN.json +74 -0
  89. sticker_convert/resources/input.zh_TW.json +74 -0
  90. sticker_convert/resources/memdump_linux.sh +25 -0
  91. sticker_convert/resources/memdump_windows.ps1 +8 -0
  92. sticker_convert/resources/output.ja_JP.json +38 -0
  93. sticker_convert/resources/output.json +24 -0
  94. sticker_convert/resources/output.zh_CN.json +38 -0
  95. sticker_convert/resources/output.zh_TW.json +38 -0
  96. sticker_convert/uploaders/compress_wastickers.py +186 -156
  97. sticker_convert/uploaders/upload_base.py +44 -35
  98. sticker_convert/uploaders/upload_signal.py +218 -173
  99. sticker_convert/uploaders/upload_telegram.py +353 -388
  100. sticker_convert/uploaders/upload_viber.py +178 -0
  101. sticker_convert/uploaders/xcode_imessage.py +295 -285
  102. sticker_convert/utils/callback.py +238 -6
  103. sticker_convert/utils/chrome_remotedebug.py +219 -0
  104. sticker_convert/utils/chromiums/linux.py +52 -0
  105. sticker_convert/utils/chromiums/osx.py +68 -0
  106. sticker_convert/utils/chromiums/windows.py +45 -0
  107. sticker_convert/utils/emoji.py +28 -0
  108. sticker_convert/utils/files/json_resources_loader.py +24 -19
  109. sticker_convert/utils/files/metadata_handler.py +8 -7
  110. sticker_convert/utils/files/run_bin.py +1 -1
  111. sticker_convert/utils/media/codec_info.py +99 -67
  112. sticker_convert/utils/media/format_verify.py +33 -20
  113. sticker_convert/utils/process.py +231 -0
  114. sticker_convert/utils/translate.py +108 -0
  115. sticker_convert/utils/url_detect.py +40 -33
  116. sticker_convert/version.py +1 -1
  117. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
  118. sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
  119. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
  120. sticker_convert/utils/auth/get_signal_auth.py +0 -129
  121. sticker_convert-2.8.12.dist-info/RECORD +0 -101
  122. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  123. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
  124. {sticker_convert-2.8.12.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:
@@ -82,3 +83,13 @@ def get_config_dir() -> Path:
82
83
 
83
84
  # Directory for saving configs
84
85
  CONFIG_DIR = get_config_dir()
86
+
87
+ # When importing SVG, import at this fps
88
+ SVG_SAMPLE_FPS = 30
89
+
90
+ # If width and height not set in SVG tag, import at this dimension
91
+ SVG_DEFAULT_WIDTH = 1024
92
+ SVG_DEFAULT_HEIGHT = 1024
93
+
94
+ # A shared dictionary
95
+ RUNTIME_STATE: Dict[str, Any] = {}
@@ -0,0 +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
+ 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,130 +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, List, Optional, Tuple
7
-
8
- import anyio
9
- import requests
10
-
11
- from sticker_convert.job_option import CredOption
12
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
13
-
14
-
15
- class DownloadBase:
16
- def __init__(
17
- self,
18
- url: str,
19
- out_dir: Path,
20
- opt_cred: Optional[CredOption],
21
- cb: CallbackProtocol,
22
- cb_return: CallbackReturn,
23
- ) -> None:
24
- self.url = url
25
- self.out_dir = out_dir
26
- self.opt_cred = opt_cred
27
- self.cb = cb
28
- self.cb_return = cb_return
29
-
30
- def download_multiple_files(
31
- self, targets: List[Tuple[str, Path]], retries: int = 3, **kwargs: Any
32
- ) -> None:
33
- anyio.run(
34
- partial(self.download_multiple_files_async, targets, retries, **kwargs)
35
- )
36
-
37
- async def download_multiple_files_async(
38
- self, targets: List[Tuple[str, Path]], retries: int = 3, **kwargs: Any
39
- ) -> None:
40
- # targets format: [(url1, dest2), (url2, dest2), ...]
41
- self.cb.put(
42
- ("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
43
- )
44
-
45
- async with anyio.create_task_group() as tg:
46
- for url, dest in targets:
47
- tg.start_soon(self.download_file_async, url, dest, retries, **kwargs)
48
-
49
- async def download_file_async(
50
- self,
51
- url: str,
52
- dest: Path,
53
- retries: int = 3,
54
- **kwargs: Any,
55
- ) -> None:
56
- for retry in range(retries):
57
- try:
58
- response = requests.get(url, allow_redirects=True, **kwargs)
59
-
60
- if not response.ok:
61
- self.cb.put("update_bar")
62
- raise requests.exceptions.RequestException(
63
- f"Error {response.status_code}"
64
- )
65
-
66
- self.cb.put(f"Downloading {url}")
67
- with open(dest, "wb+") as f:
68
- f.write(response.content)
69
- self.cb.put(f"Downloaded {url}")
70
- break
71
-
72
- except requests.exceptions.RequestException as e:
73
- self.cb.put(
74
- f"Cannot download {url} (tried {retry+1}/{retries} times): {e}"
75
- )
76
-
77
- self.cb.put("update_bar")
78
-
79
- def download_file(
80
- self,
81
- url: str,
82
- dest: Optional[Path] = None,
83
- retries: int = 3,
84
- show_progress: bool = True,
85
- **kwargs: Any,
86
- ) -> bytes:
87
- result = b""
88
- chunk_size = 102400
89
-
90
- for retry in range(retries):
91
- try:
92
- response = requests.get(
93
- url, stream=True, allow_redirects=True, **kwargs
94
- )
95
- total_length = int(response.headers.get("content-length")) # type: ignore
96
-
97
- if not response.ok:
98
- return b""
99
- self.cb.put(f"Downloading {url}")
100
-
101
- if show_progress:
102
- steps = (total_length / chunk_size) + 1
103
- self.cb.put(
104
- (
105
- "bar",
106
- None,
107
- {"set_progress_mode": "determinate", "steps": int(steps)},
108
- )
109
- )
110
-
111
- for chunk in response.iter_content(chunk_size=chunk_size):
112
- if chunk:
113
- result += chunk
114
- if show_progress:
115
- self.cb.put("update_bar")
116
-
117
- break
118
- except requests.exceptions.RequestException as e:
119
- self.cb.put(
120
- f"Cannot download {url} (tried {retry+1}/{retries} times): {e}"
121
- )
122
-
123
- if not result:
124
- return b""
125
- if dest:
126
- with open(dest, "wb+") as f:
127
- f.write(result)
128
- self.cb.put(f"Downloaded {url}")
129
- return b""
130
- 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
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Dict, List, Optional, Tuple, cast
5
+ from urllib.parse import urlparse
6
+
7
+ import requests
8
+
9
+ from sticker_convert.downloaders.download_base import DownloadBase
10
+ from sticker_convert.job_option import CredOption, InputOption
11
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
12
+ from sticker_convert.utils.emoji import extract_emojis
13
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
14
+ from sticker_convert.utils.translate import I
15
+
16
+ # References:
17
+ # https://github.com/ThaTiemsz/Discord-Emoji-Downloader/blob/master/assets/app.js
18
+ # https://github.com/zgibberish/discord-emoji-scraper/blob/main/emoji_scraper.py
19
+
20
+
21
+ class DownloadDiscord(DownloadBase):
22
+ # def __init__(self, *args: Any, **kwargs: Any) -> None:
23
+ # super().__init__(*args, **kwargs)
24
+
25
+ def download_stickers_discord(self) -> Tuple[int, int]:
26
+ if self.opt_cred is None or self.opt_cred.discord_token == "":
27
+ self.cb.put(I("Error: Downloading from Discord requires token"))
28
+ return 0, 0
29
+
30
+ gid: Optional[str] = None
31
+ if self.url.isnumeric():
32
+ gid = self.url
33
+ else:
34
+ url_parsed = urlparse(self.url)
35
+ if url_parsed.netloc == "discord.com":
36
+ gid = url_parsed.path.split("/")[2]
37
+
38
+ if gid is None or gid.isnumeric() is False:
39
+ self.cb.put(I("Error: Invalid url"))
40
+ return 0, 0
41
+
42
+ headers = {
43
+ "Authorization": self.opt_cred.discord_token,
44
+ }
45
+
46
+ r = requests.get(f"https://discord.com/api/v10/guilds/{gid}", headers=headers)
47
+ r_json = json.loads(r.text)
48
+
49
+ if self.input_option == "discord_emoji":
50
+ stickers = r_json["emojis"]
51
+ else:
52
+ stickers = r_json["stickers"]
53
+
54
+ targets: List[Tuple[str, Path]] = []
55
+ emoji_dict: Dict[str, str] = {}
56
+ for i, sticker in enumerate(stickers):
57
+ f_id = str(i).zfill(3)
58
+ sticker_id = sticker["id"]
59
+ if self.input_option == "discord_emoji":
60
+ f_ext = ".gif" if sticker["animated"] else ".png"
61
+ sticker_url = f"https://cdn.discordapp.com/emojis/{sticker_id}{f_ext}?size=4096&quality=lossless"
62
+ else:
63
+ # https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types
64
+ format_type = cast(int, sticker["format_type"])
65
+ f_ext = [".png", ".png", ".json", ".gif"][format_type - 1]
66
+ sticker_url = f"https://cdn.discordapp.com/stickers/{sticker_id}{f_ext}?size=4096&quality=lossless"
67
+ emoji_dict[f_id] = extract_emojis(sticker["tags"])
68
+ f_name = f_id + f_ext
69
+ f_path = Path(self.out_dir, f_name)
70
+ targets.append((sticker_url, f_path))
71
+
72
+ results = self.download_multiple_files(targets)
73
+
74
+ server_name = r_json["name"]
75
+ MetadataHandler.set_metadata(
76
+ self.out_dir,
77
+ title=server_name,
78
+ author=server_name,
79
+ emoji_dict=emoji_dict if self.input_option == "discord" else None,
80
+ )
81
+
82
+ return sum(results.values()), len(targets)
83
+
84
+ @staticmethod
85
+ def start(
86
+ opt_input: InputOption,
87
+ opt_cred: Optional[CredOption],
88
+ cb: CallbackProtocol,
89
+ cb_return: CallbackReturn,
90
+ ) -> Tuple[int, int]:
91
+ downloader = DownloadDiscord(opt_input, opt_cred, cb, cb_return)
92
+ return downloader.download_stickers_discord()