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
@@ -1,255 +1,417 @@
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, List, Optional, Tuple
9
- from urllib.parse import urlparse
10
-
11
- import requests
12
- from bs4 import BeautifulSoup
13
- from bs4.element import Tag
14
-
15
- from sticker_convert.downloaders.download_base import DownloadBase
16
- from sticker_convert.job_option import CredOption
17
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
18
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
19
- from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
20
-
21
-
22
- class MetadataKakao:
23
- @staticmethod
24
- def get_info_from_share_link(url: str) -> Tuple[Optional[str], Optional[str]]:
25
- headers = {"User-Agent": "Android"}
26
-
27
- response = requests.get(url, headers=headers)
28
- soup = BeautifulSoup(response.text, "html.parser")
29
-
30
- pack_title_tag = soup.find("title") # type: ignore
31
- if not pack_title_tag:
32
- return None, None
33
-
34
- pack_title: str = pack_title_tag.string # type: ignore
35
-
36
- app_scheme_link_tag = soup.find("a", id="app_scheme_link") # type: ignore
37
- assert isinstance(app_scheme_link_tag, Tag)
38
-
39
- data_urls = app_scheme_link_tag.get("data-url")
40
- if not data_urls:
41
- return None, None
42
- if isinstance(data_urls, list):
43
- data_url = data_urls[0]
44
- else:
45
- data_url = data_urls
46
-
47
- item_code = data_url.replace("kakaotalk://store/emoticon/", "").split("?")[0]
48
-
49
- return pack_title, item_code
50
-
51
- @staticmethod
52
- def get_info_from_pack_title(
53
- pack_title: str,
54
- ) -> Tuple[Optional[str], Optional[str], Optional[str]]:
55
- pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
56
-
57
- if pack_meta_r.status_code == 200:
58
- pack_meta = json.loads(pack_meta_r.text)
59
- else:
60
- return None, None, None
61
-
62
- author = pack_meta["result"]["artist"]
63
- title_ko = pack_meta["result"]["title"]
64
- thumbnail_urls = pack_meta["result"]["thumbnailUrls"]
65
-
66
- return author, title_ko, thumbnail_urls
67
-
68
- @staticmethod
69
- def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
70
- headers = {
71
- "Authorization": auth_token,
72
- }
73
-
74
- data = {"query": title_ko}
75
-
76
- response = requests.post(
77
- "https://talk-pilsner.kakao.com/emoticon/item_store/instant_search",
78
- headers=headers,
79
- data=data,
80
- )
81
-
82
- if response.status_code != 200:
83
- return None
84
-
85
- response_json = json.loads(response.text)
86
- item_code = response_json["emoticons"][0]["item_code"]
87
-
88
- return item_code
89
-
90
- @staticmethod
91
- def get_title_from_id(item_code: str, auth_token: str) -> Optional[str]:
92
- headers = {
93
- "Authorization": auth_token,
94
- }
95
-
96
- response = requests.post(
97
- f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/items/{item_code}",
98
- headers=headers,
99
- )
100
-
101
- if response.status_code != 200:
102
- return None
103
-
104
- response_json = json.loads(response.text)
105
- title = response_json["itemUnitInfo"][0]["title"]
106
- # play_path_format = response_json['itemUnitInfo'][0]['playPathFormat']
107
- # stickers_count = len(response_json['itemUnitInfo'][0]['sizes'])
108
-
109
- return title
110
-
111
-
112
- class DownloadKakao(DownloadBase):
113
- def __init__(self, *args: Any, **kwargs: Any) -> None:
114
- super().__init__(*args, **kwargs)
115
- self.pack_title: Optional[str] = None
116
- self.author: Optional[str] = None
117
-
118
- def download_stickers_kakao(self) -> bool:
119
- auth_token = None
120
- if self.opt_cred:
121
- auth_token = self.opt_cred.kakao_auth_token
122
-
123
- if urlparse(self.url).netloc == "emoticon.kakao.com":
124
- self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
125
- self.url
126
- )
127
-
128
- if item_code:
129
- return self.download_animated(item_code)
130
- self.cb.put("Download failed: Cannot download metadata for sticker pack")
131
- return False
132
-
133
- if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
134
- item_code = self.url.replace("kakaotalk://store/emoticon/", "")
135
-
136
- self.pack_title = None
137
- if auth_token:
138
- self.pack_title = MetadataKakao.get_title_from_id(item_code, auth_token)
139
- if not self.pack_title:
140
- self.cb.put("Warning: Cannot get pack_title with auth_token.")
141
- self.cb.put(
142
- "Is auth_token invalid / expired? Try to regenerate it."
143
- )
144
- self.cb.put("Continuing without getting pack_title")
145
-
146
- return self.download_animated(item_code)
147
-
148
- if urlparse(self.url).netloc == "e.kakao.com":
149
- self.pack_title = self.url.replace("https://e.kakao.com/t/", "")
150
- (
151
- self.author,
152
- title_ko,
153
- thumbnail_urls,
154
- ) = MetadataKakao.get_info_from_pack_title(self.pack_title)
155
-
156
- assert self.author
157
- assert title_ko
158
- assert thumbnail_urls
159
-
160
- if not thumbnail_urls:
161
- self.cb.put(
162
- "Download failed: Cannot download metadata for sticker pack"
163
- )
164
- return False
165
-
166
- if auth_token:
167
- item_code = MetadataKakao.get_item_code(title_ko, auth_token)
168
- if item_code:
169
- return self.download_animated(item_code)
170
- msg = "Warning: Cannot get item code.\n"
171
- msg += "Is auth_token invalid / expired? Try to regenerate it.\n"
172
- msg += "Continue to download static stickers instead?"
173
- self.cb.put(("ask_bool", (msg,), None))
174
- if self.cb_return:
175
- response = self.cb_return.get_response()
176
- else:
177
- response = False
178
-
179
- if response is False:
180
- return False
181
-
182
- return self.download_static(thumbnail_urls)
183
-
184
- self.cb.put("Download failed: Unrecognized URL")
185
- return False
186
-
187
- def download_static(self, thumbnail_urls: str) -> bool:
188
- MetadataHandler.set_metadata(
189
- self.out_dir, title=self.pack_title, author=self.author
190
- )
191
-
192
- targets: List[Tuple[str, Path]] = []
193
-
194
- for num, url in enumerate(thumbnail_urls):
195
- dest = Path(self.out_dir, str(num).zfill(3) + ".png")
196
- targets.append((url, dest))
197
-
198
- self.download_multiple_files(targets)
199
-
200
- return True
201
-
202
- def download_animated(self, item_code: str) -> bool:
203
- MetadataHandler.set_metadata(
204
- self.out_dir, title=self.pack_title, author=self.author
205
- )
206
-
207
- pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
208
-
209
- zip_file = self.download_file(pack_url)
210
- if zip_file:
211
- self.cb.put(f"Downloaded {pack_url}")
212
- else:
213
- self.cb.put(f"Cannot download {pack_url}")
214
- return False
215
-
216
- with zipfile.ZipFile(BytesIO(zip_file)) as zf:
217
- self.cb.put("Unzipping...")
218
- self.cb.put(
219
- (
220
- "bar",
221
- None,
222
- {"set_progress_mode": "determinate", "steps": len(zf.namelist())},
223
- )
224
- )
225
-
226
- for num, f_path in enumerate(sorted(zf.namelist())):
227
- ext = Path(f_path).suffix
228
-
229
- if ext in (".gif", ".webp"):
230
- data = DecryptKakao.xor_data(zf.read(f_path))
231
- self.cb.put(f"Decrypted {f_path}")
232
- else:
233
- data = zf.read(f_path)
234
- self.cb.put(f"Read {f_path}")
235
-
236
- out_path = Path(self.out_dir, str(num).zfill(3) + ext)
237
- with open(out_path, "wb") as f:
238
- f.write(data)
239
-
240
- self.cb.put("update_bar")
241
-
242
- self.cb.put(f"Finished getting {pack_url}")
243
-
244
- return True
245
-
246
- @staticmethod
247
- def start(
248
- url: str,
249
- out_dir: Path,
250
- opt_cred: Optional[CredOption],
251
- cb: CallbackProtocol,
252
- cb_return: CallbackReturn,
253
- ) -> bool:
254
- downloader = DownloadKakao(url, out_dir, opt_cred, cb, cb_return)
255
- return downloader.download_stickers_kakao()
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import itertools
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+ from urllib.parse import urlparse
9
+
10
+ import requests
11
+
12
+ from sticker_convert.downloaders.download_base import DownloadBase
13
+ from sticker_convert.job_option import CredOption, InputOption
14
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
15
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
16
+ from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
17
+ from sticker_convert.utils.translate import I
18
+
19
+
20
+ class MetadataKakao:
21
+ @staticmethod
22
+ def share_link_to_public_link(share_link: str) -> str:
23
+ # Share link redirect to preview link if use desktop headers
24
+ headers_desktop = {"User-Agent": "Chrome"}
25
+ r = requests.get(share_link, headers=headers_desktop, allow_redirects=True)
26
+ return r.url
27
+
28
+ @staticmethod
29
+ def get_item_code_from_hash(hash: str, auth_token: str) -> Optional[str]:
30
+ headers = {
31
+ "Authorization": auth_token,
32
+ }
33
+
34
+ data = {"hashedItemCode": hash}
35
+
36
+ response = requests.post(
37
+ "https://talk-pilsner.kakao.com/emoticon/api/store/v3/item-code-by-hash",
38
+ headers=headers,
39
+ data=data,
40
+ )
41
+
42
+ if response.status_code != 200:
43
+ return None
44
+
45
+ response_json = json.loads(response.text)
46
+ item_code = response_json["itemCode"]
47
+
48
+ return item_code
49
+
50
+ @staticmethod
51
+ def get_item_code_from_search(
52
+ pack_title: str, search_term: str, by_author: bool, auth_token: str
53
+ ) -> str:
54
+ headers = {
55
+ "Authorization": auth_token,
56
+ }
57
+
58
+ data = {"query": search_term}
59
+
60
+ response = requests.post(
61
+ "https://talk-pilsner.kakao.com/emoticon/item_store/instant_search",
62
+ headers=headers,
63
+ data=data,
64
+ )
65
+
66
+ if response.status_code != 200:
67
+ return "auth_error"
68
+
69
+ def check_pack_match(pack_info: Dict[str, Any]) -> bool:
70
+ share_link = pack_info["itemMetaInfo"]["shareData"]["linkUrl"]
71
+ public_url = MetadataKakao.share_link_to_public_link(share_link)
72
+ if pack_title == urlparse(public_url).path.split("/")[-1]:
73
+ return True
74
+ return False
75
+
76
+ response_json = json.loads(response.text)
77
+ for emoticon in response_json["emoticons"]:
78
+ item_code = emoticon["item_code"]
79
+ pack_info = MetadataKakao.get_pack_info_authed(item_code, auth_token)
80
+ if pack_info is None:
81
+ continue
82
+ if check_pack_match(pack_info):
83
+ return item_code
84
+ if by_author:
85
+ cid = pack_info["itemMetaInfo"]["itemMetaData"]["cid"]
86
+ for item_code in MetadataKakao.get_items_by_creator(cid, auth_token):
87
+ pack_info = MetadataKakao.get_pack_info_authed(
88
+ item_code, auth_token
89
+ )
90
+ if pack_info is None:
91
+ continue
92
+ if check_pack_match(pack_info):
93
+ return item_code
94
+
95
+ return "code_not_found"
96
+
97
+ @staticmethod
98
+ def get_items_by_creator(cid: str, auth_token: str) -> List[str]:
99
+ headers = {"Authorization": auth_token}
100
+
101
+ params = {
102
+ "itemSort": "NEW",
103
+ "offset": "0",
104
+ "size": "30",
105
+ }
106
+
107
+ response = requests.get(
108
+ f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/creators/{cid}",
109
+ headers=headers,
110
+ params=params,
111
+ )
112
+
113
+ return [i["item_id"] for i in json.loads(response.text)["items"]]
114
+
115
+ @staticmethod
116
+ def get_pack_info_unauthed(
117
+ pack_title: str,
118
+ ) -> Optional[dict[str, Any]]:
119
+ pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
120
+
121
+ if pack_meta_r.status_code == 200:
122
+ pack_meta = json.loads(pack_meta_r.text)
123
+ else:
124
+ return None
125
+
126
+ return pack_meta
127
+
128
+ @staticmethod
129
+ def get_pack_info_authed(
130
+ item_code: str, auth_token: str
131
+ ) -> Optional[dict[str, Any]]:
132
+ headers = {
133
+ "Authorization": auth_token,
134
+ "Talk-Agent": "android/10.8.1",
135
+ "Talk-Language": "en",
136
+ "User-Agent": "okhttp/4.10.0",
137
+ }
138
+
139
+ response = requests.post(
140
+ f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/items/{item_code}",
141
+ headers=headers,
142
+ )
143
+
144
+ if response.status_code != 200:
145
+ return None
146
+
147
+ response_json = json.loads(response.text)
148
+
149
+ return response_json
150
+
151
+
152
+ class DownloadKakao(DownloadBase):
153
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
154
+ self.MSG_AUTH_ERROR = I(
155
+ "Warning: Cannot get item code.\n"
156
+ "Is auth_token invalid / expired? Try to regenerate it.\n"
157
+ "Continue to download static stickers instead?"
158
+ )
159
+
160
+ self.MSG_CODE_NOT_FOUND = I(
161
+ "Warning: Cannot get item code.\n"
162
+ "Please use share link instead.\n"
163
+ "Continue to download static stickers instead?"
164
+ )
165
+
166
+ super().__init__(*args, **kwargs)
167
+ self.pack_title: Optional[str] = None
168
+ self.author: Optional[str] = None
169
+ self.auth_token: Optional[str] = None
170
+
171
+ self.pack_info_unauthed: Optional[dict[str, Any]] = None
172
+ self.pack_info_authed: Optional[dict[str, Any]] = None
173
+
174
+ def download_stickers_kakao(self) -> Tuple[int, int]:
175
+ self.auth_token = None
176
+ if self.opt_cred:
177
+ self.auth_token = self.opt_cred.kakao_auth_token
178
+
179
+ if urlparse(self.url).netloc == "emoticon.kakao.com":
180
+ item_code = None
181
+ if self.auth_token is not None:
182
+ hash = urlparse(self.url).path.split("/")[-1]
183
+ item_code = MetadataKakao.get_item_code_from_hash(hash, self.auth_token)
184
+
185
+ public_url = MetadataKakao.share_link_to_public_link(self.url)
186
+ self.pack_title = urlparse(public_url).path.split("/")[-1]
187
+ pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(self.pack_title)
188
+ if pack_info_unauthed is None:
189
+ self.cb.put(
190
+ I("Download failed: Cannot download metadata for sticker pack")
191
+ )
192
+ return 0, 0
193
+
194
+ self.author = pack_info_unauthed["result"]["artist"]
195
+ thumbnail_urls = pack_info_unauthed["result"]["thumbnailUrls"]
196
+
197
+ if item_code is None:
198
+ if self.auth_token is None:
199
+ self.cb.put(
200
+ I("Warning: Downloading animated sticker requires auth_token")
201
+ )
202
+ else:
203
+ self.cb.put(
204
+ I(
205
+ "Warning: auth_token invalid, cannot download animated sticker"
206
+ )
207
+ )
208
+ self.cb.put(I("Downloading static stickers..."))
209
+ return self.download_static(thumbnail_urls)
210
+ else:
211
+ return self.download_animated(item_code)
212
+
213
+ if self.url.isnumeric():
214
+ self.pack_title = None
215
+ if self.auth_token:
216
+ self.pack_info_authed = MetadataKakao.get_pack_info_authed(
217
+ self.url, self.auth_token
218
+ )
219
+ if self.pack_info_authed:
220
+ self.pack_title = self.pack_info_authed["itemUnitInfo"][0]["title"]
221
+ else:
222
+ self.cb.put(I("Warning: Cannot get pack_title with auth_token."))
223
+ self.cb.put(
224
+ I("Is auth_token invalid / expired? Try to regenerate it.")
225
+ )
226
+ self.cb.put(I("Continuing without getting pack_title"))
227
+
228
+ return self.download_animated(self.url)
229
+
230
+ if urlparse(self.url).netloc == "e.kakao.com":
231
+ self.pack_title = urlparse(self.url).path.split("/")[-1]
232
+ self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
233
+ self.pack_title
234
+ )
235
+
236
+ if not self.pack_info_unauthed:
237
+ self.cb.put(
238
+ I("Download failed: Cannot download metadata for sticker pack")
239
+ )
240
+ return 0, 0
241
+
242
+ self.author = self.pack_info_unauthed["result"]["artist"]
243
+ title_ko = self.pack_info_unauthed["result"]["title"]
244
+ thumbnail_urls = self.pack_info_unauthed["result"]["thumbnailUrls"]
245
+
246
+ if self.auth_token:
247
+ item_code = MetadataKakao.get_item_code_from_search(
248
+ self.pack_title, title_ko, False, self.auth_token
249
+ )
250
+ if item_code == "auth_error":
251
+ msg = self.MSG_AUTH_ERROR
252
+ elif item_code == "code_not_found":
253
+ self.cb.put(
254
+ I(
255
+ "Cannot get item code, trying to search by author name, this may take long time..."
256
+ )
257
+ )
258
+ self.cb.put(
259
+ I("Hint: Use share link instead to download more reliably")
260
+ )
261
+ if self.author is not None:
262
+ item_code = MetadataKakao.get_item_code_from_search(
263
+ self.pack_title, self.author, True, self.auth_token
264
+ )
265
+ if item_code == "code_not_found":
266
+ msg = self.MSG_CODE_NOT_FOUND
267
+ else:
268
+ return self.download_animated(item_code)
269
+ else:
270
+ return self.download_animated(item_code)
271
+
272
+ self.cb.put(("ask_bool", (msg,), None))
273
+ if self.cb_return:
274
+ response = self.cb_return.get_response()
275
+ else:
276
+ response = False
277
+
278
+ if response is False:
279
+ return 0, 0
280
+
281
+ return self.download_static(thumbnail_urls)
282
+
283
+ self.cb.put(I("Download failed: Unrecognized URL"))
284
+ return 0, 0
285
+
286
+ def download_static(self, thumbnail_urls: str) -> Tuple[int, int]:
287
+ headers = {"User-Agent": "Android"}
288
+ MetadataHandler.set_metadata(
289
+ self.out_dir, title=self.pack_title, author=self.author
290
+ )
291
+
292
+ targets: List[Tuple[str, Path]] = []
293
+
294
+ for num, url in enumerate(thumbnail_urls):
295
+ dest = Path(self.out_dir, str(num).zfill(3) + ".png")
296
+ targets.append((url, dest))
297
+
298
+ results = self.download_multiple_files(targets, headers=headers)
299
+
300
+ return sum(results.values()), len(targets)
301
+
302
+ def download_animated(self, item_code: str) -> Tuple[int, int]:
303
+ MetadataHandler.set_metadata(
304
+ self.out_dir, title=self.pack_title, author=self.author
305
+ )
306
+
307
+ play_exts = [".webp", ".gif", ".png", ""]
308
+ play_types = ["emot", "emoji", ""] # emot = normal; emoji = mini
309
+ play_path_format = None
310
+ sound_exts = [".mp3", ""]
311
+ sound_path_format = None
312
+ stickers_count = 32 # https://emoticonstudio.kakao.com/pages/start
313
+
314
+ headers = {"User-Agent": "Android"}
315
+
316
+ if not self.pack_info_authed and self.auth_token:
317
+ self.pack_info_authed = MetadataKakao.get_pack_info_authed(
318
+ item_code, self.auth_token
319
+ )
320
+ if self.pack_info_authed:
321
+ preview_data = self.pack_info_authed["itemUnitInfo"][0]["previewData"]
322
+ play_path_format = preview_data["playPathFormat"]
323
+ sound_path_format = preview_data["soundPathFormat"]
324
+ stickers_count = preview_data["num"]
325
+ else:
326
+ if not self.pack_info_unauthed:
327
+ public_url = None
328
+ if urlparse(self.url).netloc == "emoticon.kakao.com":
329
+ public_url = MetadataKakao.share_link_to_public_link(self.url)
330
+ elif urlparse(self.url).netloc == "e.kakao.com":
331
+ public_url = self.url
332
+ if public_url:
333
+ pack_title = urlparse(public_url).path.split("/")[-1]
334
+ self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
335
+ pack_title
336
+ )
337
+
338
+ if self.pack_info_unauthed:
339
+ stickers_count = len(self.pack_info_unauthed["result"]["thumbnailUrls"])
340
+
341
+ play_type = ""
342
+ play_ext = ""
343
+ if play_path_format is None:
344
+ for play_type, play_ext in itertools.product(play_types, play_exts):
345
+ r = requests.get(
346
+ f"https://item.kakaocdn.net/dw/{item_code}.{play_type}_001{play_ext}",
347
+ headers=headers,
348
+ )
349
+ if r.ok:
350
+ break
351
+ if play_ext == "":
352
+ self.cb.put(I("Failed to determine extension of {}").format(item_code))
353
+ return 0, 0
354
+ else:
355
+ play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
356
+ else:
357
+ play_ext = "." + play_path_format.split(".")[-1]
358
+
359
+ sound_ext = ""
360
+ if sound_path_format is None:
361
+ for sound_ext in sound_exts:
362
+ r = requests.get(
363
+ f"https://item.kakaocdn.net/dw/{item_code}.sound_001{sound_ext}",
364
+ headers=headers,
365
+ )
366
+ if r.ok:
367
+ break
368
+ if sound_ext != "":
369
+ sound_path_format = f"dw/{item_code}.sound_0##{sound_ext}"
370
+ elif sound_path_format != "":
371
+ sound_ext = "." + sound_path_format.split(".")[-1]
372
+
373
+ assert play_path_format
374
+ targets: list[tuple[str, Path]] = []
375
+ for num in range(1, stickers_count + 1):
376
+ play_url = "https://item.kakaocdn.net/" + play_path_format.replace(
377
+ "##", str(num).zfill(2)
378
+ )
379
+ play_dl_path = Path(self.out_dir, str(num).zfill(3) + play_ext)
380
+ targets.append((play_url, play_dl_path))
381
+
382
+ if sound_path_format:
383
+ sound_url = "https://item.kakaocdn.net/" + sound_path_format.replace(
384
+ "##", str(num).zfill(2)
385
+ )
386
+ sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
387
+ targets.append((sound_url, sound_dl_path))
388
+
389
+ results = self.download_multiple_files(targets, headers=headers)
390
+
391
+ for target in targets:
392
+ f_path = target[1]
393
+ ext = Path(f_path).suffix
394
+
395
+ if ext not in (".gif", ".webp"):
396
+ continue
397
+
398
+ with open(f_path, "rb") as f:
399
+ data = f.read()
400
+ data = DecryptKakao.xor_data(data)
401
+ self.cb.put(I("Decrypted {}").format(f_path))
402
+ with open(f_path, "wb+") as f:
403
+ f.write(data)
404
+
405
+ self.cb.put(I("Finished getting {}").format(item_code))
406
+
407
+ return sum(results.values()), len(targets)
408
+
409
+ @staticmethod
410
+ def start(
411
+ opt_input: InputOption,
412
+ opt_cred: Optional[CredOption],
413
+ cb: CallbackProtocol,
414
+ cb_return: CallbackReturn,
415
+ ) -> Tuple[int, int]:
416
+ downloader = DownloadKakao(opt_input, opt_cred, cb, cb_return)
417
+ return downloader.download_stickers_kakao()