sticker-convert 2.8.12__py3-none-any.whl → 2.8.14__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.
- sticker_convert/downloaders/download_kakao.py +221 -48
- sticker_convert/version.py +1 -1
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/METADATA +3 -3
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/RECORD +8 -8
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/LICENSE +0 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/WHEEL +0 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.8.14.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,13 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
|
+
import itertools
|
4
5
|
import json
|
6
|
+
import re
|
5
7
|
import zipfile
|
6
8
|
from io import BytesIO
|
7
9
|
from pathlib import Path
|
8
|
-
from typing import Any, List, Optional, Tuple
|
10
|
+
from typing import Any, List, Optional, Tuple, cast
|
9
11
|
from urllib.parse import urlparse
|
10
12
|
|
11
13
|
import requests
|
@@ -19,6 +21,26 @@ from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
|
19
21
|
from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
|
20
22
|
|
21
23
|
|
24
|
+
def search_bracket(text: str, open_bracket: str = "{", close_bracket: str = "}") -> int:
|
25
|
+
depth = 0
|
26
|
+
is_str = False
|
27
|
+
|
28
|
+
for count, char in enumerate(text):
|
29
|
+
if char == '"':
|
30
|
+
is_str = not is_str
|
31
|
+
|
32
|
+
if is_str is False:
|
33
|
+
if char == open_bracket:
|
34
|
+
depth += 1
|
35
|
+
elif char == close_bracket:
|
36
|
+
depth -= 1
|
37
|
+
|
38
|
+
if depth == 0:
|
39
|
+
return count
|
40
|
+
|
41
|
+
return -1
|
42
|
+
|
43
|
+
|
22
44
|
class MetadataKakao:
|
23
45
|
@staticmethod
|
24
46
|
def get_info_from_share_link(url: str) -> Tuple[Optional[str], Optional[str]]:
|
@@ -36,34 +58,55 @@ class MetadataKakao:
|
|
36
58
|
app_scheme_link_tag = soup.find("a", id="app_scheme_link") # type: ignore
|
37
59
|
assert isinstance(app_scheme_link_tag, Tag)
|
38
60
|
|
39
|
-
|
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]
|
61
|
+
item_code_fake = cast(str, app_scheme_link_tag["data-i"])
|
48
62
|
|
49
|
-
|
63
|
+
js = ""
|
64
|
+
for script_tag in soup.find_all("script"):
|
65
|
+
js = script_tag.string
|
66
|
+
if js and "emoticonDeepLink" in js:
|
67
|
+
break
|
68
|
+
if "emoticonDeepLink" not in js:
|
69
|
+
return None, None
|
50
70
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
71
|
+
func_start_pos = js.find("function emoticonDeepLink(")
|
72
|
+
js = js[func_start_pos:]
|
73
|
+
bracket_start_pos = js.find("{")
|
74
|
+
func_end_pos = search_bracket(js[bracket_start_pos:]) + bracket_start_pos
|
75
|
+
js = js[bracket_start_pos + 1 : func_end_pos]
|
76
|
+
js = js.split(";")[0]
|
56
77
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
78
|
+
minus_num_regex = re.search(r"\-(.*?)\^", js)
|
79
|
+
if not minus_num_regex:
|
80
|
+
return None, None
|
81
|
+
minus_num_str = minus_num_regex.group(1)
|
82
|
+
if not minus_num_str.isnumeric():
|
83
|
+
return None, None
|
84
|
+
minus_num = int(minus_num_str)
|
61
85
|
|
62
|
-
|
63
|
-
|
64
|
-
|
86
|
+
xor_num_regex = re.search(r"\^(.*?)\)", js)
|
87
|
+
if not xor_num_regex:
|
88
|
+
return None, None
|
89
|
+
xor_num_str = xor_num_regex.group(1)
|
90
|
+
if not xor_num_str.isnumeric():
|
91
|
+
return None, None
|
92
|
+
xor_num = int(xor_num_str)
|
93
|
+
|
94
|
+
item_code = str(int(item_code_fake) - minus_num ^ xor_num)
|
95
|
+
|
96
|
+
# https://github.com/Nuitka/Nuitka/issues/385
|
97
|
+
# js2py not working if compiled by nuitka
|
98
|
+
# web2app_start_pos = js.find("daumtools.web2app(")
|
99
|
+
# js = js[:web2app_start_pos] + "return a;}"
|
100
|
+
# get_item_code = js2py.eval_js(js) # type: ignore
|
101
|
+
# kakao_scheme_link = cast(
|
102
|
+
# str,
|
103
|
+
# get_item_code(
|
104
|
+
# "kakaotalk://store/emoticon/${i}?referer=share_link", item_code_fake
|
105
|
+
# ),
|
106
|
+
# )
|
107
|
+
# item_code = urlparse(kakao_scheme_link).path.split("/")[-1]
|
65
108
|
|
66
|
-
return
|
109
|
+
return pack_title, item_code
|
67
110
|
|
68
111
|
@staticmethod
|
69
112
|
def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
|
@@ -88,9 +131,27 @@ class MetadataKakao:
|
|
88
131
|
return item_code
|
89
132
|
|
90
133
|
@staticmethod
|
91
|
-
def
|
134
|
+
def get_pack_info_unauthed(
|
135
|
+
pack_title: str,
|
136
|
+
) -> Optional[dict[str, Any]]:
|
137
|
+
pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
|
138
|
+
|
139
|
+
if pack_meta_r.status_code == 200:
|
140
|
+
pack_meta = json.loads(pack_meta_r.text)
|
141
|
+
else:
|
142
|
+
return None
|
143
|
+
|
144
|
+
return pack_meta
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def get_pack_info_authed(
|
148
|
+
item_code: str, auth_token: str
|
149
|
+
) -> Optional[dict[str, Any]]:
|
92
150
|
headers = {
|
93
151
|
"Authorization": auth_token,
|
152
|
+
"Talk-Agent": "android/10.8.1",
|
153
|
+
"Talk-Language": "en",
|
154
|
+
"User-Agent": "okhttp/4.10.0",
|
94
155
|
}
|
95
156
|
|
96
157
|
response = requests.post(
|
@@ -102,11 +163,8 @@ class MetadataKakao:
|
|
102
163
|
return None
|
103
164
|
|
104
165
|
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
166
|
|
109
|
-
return
|
167
|
+
return response_json
|
110
168
|
|
111
169
|
|
112
170
|
class DownloadKakao(DownloadBase):
|
@@ -114,11 +172,15 @@ class DownloadKakao(DownloadBase):
|
|
114
172
|
super().__init__(*args, **kwargs)
|
115
173
|
self.pack_title: Optional[str] = None
|
116
174
|
self.author: Optional[str] = None
|
175
|
+
self.auth_token: Optional[str] = None
|
176
|
+
|
177
|
+
self.pack_info_unauthed: Optional[dict[str, Any]] = None
|
178
|
+
self.pack_info_authed: Optional[dict[str, Any]] = None
|
117
179
|
|
118
180
|
def download_stickers_kakao(self) -> bool:
|
119
|
-
auth_token = None
|
181
|
+
self.auth_token = None
|
120
182
|
if self.opt_cred:
|
121
|
-
auth_token = self.opt_cred.kakao_auth_token
|
183
|
+
self.auth_token = self.opt_cred.kakao_auth_token
|
122
184
|
|
123
185
|
if urlparse(self.url).netloc == "emoticon.kakao.com":
|
124
186
|
self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
|
@@ -134,9 +196,13 @@ class DownloadKakao(DownloadBase):
|
|
134
196
|
item_code = self.url.replace("kakaotalk://store/emoticon/", "")
|
135
197
|
|
136
198
|
self.pack_title = None
|
137
|
-
if auth_token:
|
138
|
-
self.
|
139
|
-
|
199
|
+
if self.auth_token:
|
200
|
+
self.pack_info_authed = MetadataKakao.get_pack_info_authed(
|
201
|
+
item_code, self.auth_token
|
202
|
+
)
|
203
|
+
if self.pack_info_authed:
|
204
|
+
self.pack_title = self.pack_info_authed["itemUnitInfo"][0]["title"]
|
205
|
+
else:
|
140
206
|
self.cb.put("Warning: Cannot get pack_title with auth_token.")
|
141
207
|
self.cb.put(
|
142
208
|
"Is auth_token invalid / expired? Try to regenerate it."
|
@@ -146,25 +212,23 @@ class DownloadKakao(DownloadBase):
|
|
146
212
|
return self.download_animated(item_code)
|
147
213
|
|
148
214
|
if urlparse(self.url).netloc == "e.kakao.com":
|
149
|
-
self.pack_title = self.url.
|
150
|
-
(
|
151
|
-
self.
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
assert self.author
|
157
|
-
assert title_ko
|
158
|
-
assert thumbnail_urls
|
159
|
-
|
160
|
-
if not thumbnail_urls:
|
215
|
+
self.pack_title = urlparse(self.url).path.split("/")[-1]
|
216
|
+
self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
|
217
|
+
self.pack_title
|
218
|
+
)
|
219
|
+
|
220
|
+
if not self.pack_info_unauthed:
|
161
221
|
self.cb.put(
|
162
222
|
"Download failed: Cannot download metadata for sticker pack"
|
163
223
|
)
|
164
224
|
return False
|
165
225
|
|
166
|
-
|
167
|
-
|
226
|
+
self.author = self.pack_info_unauthed["result"]["artist"]
|
227
|
+
title_ko = self.pack_info_unauthed["result"]["title"]
|
228
|
+
thumbnail_urls = self.pack_info_unauthed["result"]["thumbnailUrls"]
|
229
|
+
|
230
|
+
if self.auth_token:
|
231
|
+
item_code = MetadataKakao.get_item_code(title_ko, self.auth_token)
|
168
232
|
if item_code:
|
169
233
|
return self.download_animated(item_code)
|
170
234
|
msg = "Warning: Cannot get item code.\n"
|
@@ -204,6 +268,115 @@ class DownloadKakao(DownloadBase):
|
|
204
268
|
self.out_dir, title=self.pack_title, author=self.author
|
205
269
|
)
|
206
270
|
|
271
|
+
success = self.download_animated_zip(item_code)
|
272
|
+
if not success:
|
273
|
+
self.cb.put("Trying to download one by one")
|
274
|
+
success = self.download_animated_files(item_code)
|
275
|
+
|
276
|
+
return success
|
277
|
+
|
278
|
+
def download_animated_files(self, item_code: str) -> bool:
|
279
|
+
play_exts = [".webp", ".gif", ".png", ""]
|
280
|
+
play_types = ["emot", "emoji", ""] # emot = normal; emoji = mini
|
281
|
+
play_path_format = None
|
282
|
+
sound_exts = [".mp3", ""]
|
283
|
+
sound_path_format = None
|
284
|
+
stickers_count = 32 # https://emoticonstudio.kakao.com/pages/start
|
285
|
+
|
286
|
+
if not self.pack_info_authed and self.auth_token:
|
287
|
+
self.pack_info_authed = MetadataKakao.get_pack_info_authed(
|
288
|
+
item_code, self.auth_token
|
289
|
+
)
|
290
|
+
if self.pack_info_authed:
|
291
|
+
preview_data = self.pack_info_authed["itemUnitInfo"][0]["previewData"]
|
292
|
+
play_path_format = preview_data["playPathFormat"]
|
293
|
+
sound_path_format = preview_data["soundPathFormat"]
|
294
|
+
stickers_count = preview_data["num"]
|
295
|
+
else:
|
296
|
+
if not self.pack_info_unauthed:
|
297
|
+
public_url = None
|
298
|
+
if urlparse(self.url).netloc == "emoticon.kakao.com":
|
299
|
+
r = requests.get(self.url)
|
300
|
+
# Share url would redirect to public url without headers
|
301
|
+
public_url = r.url
|
302
|
+
elif urlparse(self.url).netloc == "e.kakao.com":
|
303
|
+
public_url = self.url
|
304
|
+
if public_url:
|
305
|
+
pack_title = urlparse(public_url).path.split("/")[-1]
|
306
|
+
self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
|
307
|
+
pack_title
|
308
|
+
)
|
309
|
+
|
310
|
+
if self.pack_info_unauthed:
|
311
|
+
stickers_count = len(self.pack_info_unauthed["result"]["thumbnailUrls"])
|
312
|
+
|
313
|
+
play_type = ""
|
314
|
+
play_ext = ""
|
315
|
+
if play_path_format is None:
|
316
|
+
for play_type, play_ext in itertools.product(play_types, play_exts):
|
317
|
+
r = requests.get(
|
318
|
+
f"https://item.kakaocdn.net/dw/{item_code}.{play_type}_001{play_ext}"
|
319
|
+
)
|
320
|
+
if r.ok:
|
321
|
+
break
|
322
|
+
if play_ext == "":
|
323
|
+
self.cb.put(f"Failed to determine extension of {item_code}")
|
324
|
+
return False
|
325
|
+
else:
|
326
|
+
play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
|
327
|
+
else:
|
328
|
+
play_ext = "." + play_path_format.split(".")[-1]
|
329
|
+
|
330
|
+
sound_ext = ""
|
331
|
+
if sound_path_format is None:
|
332
|
+
for sound_ext in sound_exts:
|
333
|
+
r = requests.get(
|
334
|
+
f"https://item.kakaocdn.net/dw/{item_code}.sound_001{sound_ext}"
|
335
|
+
)
|
336
|
+
if r.ok:
|
337
|
+
break
|
338
|
+
if sound_ext != "":
|
339
|
+
sound_path_format = f"dw/{item_code}.sound_0##{sound_ext}"
|
340
|
+
elif sound_path_format != "":
|
341
|
+
sound_ext = "." + sound_path_format.split(".")[-1]
|
342
|
+
|
343
|
+
assert play_path_format
|
344
|
+
targets: list[tuple[str, Path]] = []
|
345
|
+
for num in range(1, stickers_count + 1):
|
346
|
+
play_url = "https://item.kakaocdn.net/" + play_path_format.replace(
|
347
|
+
"##", str(num).zfill(2)
|
348
|
+
)
|
349
|
+
play_dl_path = Path(self.out_dir, str(num).zfill(3) + play_ext)
|
350
|
+
targets.append((play_url, play_dl_path))
|
351
|
+
|
352
|
+
if sound_path_format:
|
353
|
+
sound_url = "https://item.kakaocdn.net/" + sound_path_format.replace(
|
354
|
+
"##", str(num).zfill(2)
|
355
|
+
)
|
356
|
+
sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
|
357
|
+
targets.append((sound_url, sound_dl_path))
|
358
|
+
|
359
|
+
self.download_multiple_files(targets)
|
360
|
+
|
361
|
+
for target in targets:
|
362
|
+
f_path = target[1]
|
363
|
+
ext = Path(f_path).suffix
|
364
|
+
|
365
|
+
if ext not in (".gif", ".webp"):
|
366
|
+
continue
|
367
|
+
|
368
|
+
with open(f_path, "rb") as f:
|
369
|
+
data = f.read()
|
370
|
+
data = DecryptKakao.xor_data(data)
|
371
|
+
self.cb.put(f"Decrypted {f_path}")
|
372
|
+
with open(f_path, "wb+") as f:
|
373
|
+
f.write(data)
|
374
|
+
|
375
|
+
self.cb.put(f"Finished getting {item_code}")
|
376
|
+
|
377
|
+
return True
|
378
|
+
|
379
|
+
def download_animated_zip(self, item_code: str) -> bool:
|
207
380
|
pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
|
208
381
|
|
209
382
|
zip_file = self.download_file(pack_url)
|
sticker_convert/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sticker-convert
|
3
|
-
Version: 2.8.
|
3
|
+
Version: 2.8.14
|
4
4
|
Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, iMessage. Written in Python.
|
5
5
|
Author-email: laggykiller <chaudominic2@gmail.com>
|
6
6
|
Maintainer-email: laggykiller <chaudominic2@gmail.com>
|
@@ -377,8 +377,8 @@ Requires-Dist: numpy >=1.22.4
|
|
377
377
|
Requires-Dist: Pillow ~=10.3.0
|
378
378
|
Requires-Dist: pyoxipng ~=9.0.0
|
379
379
|
Requires-Dist: python-telegram-bot ~=21.2
|
380
|
-
Requires-Dist: requests ~=2.32.
|
381
|
-
Requires-Dist: rlottie-python ~=1.3.
|
380
|
+
Requires-Dist: requests ~=2.32.3
|
381
|
+
Requires-Dist: rlottie-python ~=1.3.5
|
382
382
|
Requires-Dist: signalstickers-client-fork-laggykiller ~=3.3.0.post2
|
383
383
|
Requires-Dist: sqlcipher3-wheels ~=0.5.2.post1
|
384
384
|
Requires-Dist: tqdm ~=4.66.4
|
@@ -6,10 +6,10 @@ sticker_convert/definitions.py,sha256=ZhP2ALCEud-w9ZZD4c3TDG9eHGPZyaAL7zPUsJAbjt
|
|
6
6
|
sticker_convert/gui.py,sha256=TRPGwMhSMPHnZppHmw2OWHKTJtGoeLpGWD0eRYi4_yk,30707
|
7
7
|
sticker_convert/job.py,sha256=vKv1--y4MVmZV_IBpUhEfNEiUeEqrTR1umzlALPXKdw,25775
|
8
8
|
sticker_convert/job_option.py,sha256=JHAFCxp7-dDwD-1PbpYLAFRF3OoJu8cj_BjOm5r8Gp8,7732
|
9
|
-
sticker_convert/version.py,sha256=
|
9
|
+
sticker_convert/version.py,sha256=xfnmuNP-PsbPT9tAIe1hZe8BVTotwNbxoJXlFmvcuP4,47
|
10
10
|
sticker_convert/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
sticker_convert/downloaders/download_base.py,sha256=x18bI2mPpbXRnSmStBHEb1IvN-VPCilOHLQUs6YPUEU,4041
|
12
|
-
sticker_convert/downloaders/download_kakao.py,sha256=
|
12
|
+
sticker_convert/downloaders/download_kakao.py,sha256=hHilDDzYaGU5BQmOwMF0NKnsysxh8loybfoFSNQQdSw,14955
|
13
13
|
sticker_convert/downloaders/download_line.py,sha256=9WzOWujTbZdAqBi52k21OUEfRmcV1loCaJiDmg6dklw,17853
|
14
14
|
sticker_convert/downloaders/download_signal.py,sha256=PfwscdbcEd_5C3Ecs0F8Qc8si1sLzLodAdnsHVwXgac,3063
|
15
15
|
sticker_convert/downloaders/download_telegram.py,sha256=jufMqc78aXOPDr7fQf9ykkNyhQ7KVCp4gRBxs09NgMo,4614
|
@@ -93,9 +93,9 @@ sticker_convert/utils/media/apple_png_normalize.py,sha256=LbrQhc7LlYX4I9ek4XJsZE
|
|
93
93
|
sticker_convert/utils/media/codec_info.py,sha256=1QfW3wgZ5vOk7T4XtLHYvJK1x8RbASRPSvhKEPkcu9A,15747
|
94
94
|
sticker_convert/utils/media/decrypt_kakao.py,sha256=4wq9ZDRnFkx1WmFZnyEogBofiLGsWQM_X69HlA36578,1947
|
95
95
|
sticker_convert/utils/media/format_verify.py,sha256=Xf94jyqk_6M9IlFGMy0wYIgQKn_yg00nD4XW0CgAbew,5732
|
96
|
-
sticker_convert-2.8.
|
97
|
-
sticker_convert-2.8.
|
98
|
-
sticker_convert-2.8.
|
99
|
-
sticker_convert-2.8.
|
100
|
-
sticker_convert-2.8.
|
101
|
-
sticker_convert-2.8.
|
96
|
+
sticker_convert-2.8.14.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
97
|
+
sticker_convert-2.8.14.dist-info/METADATA,sha256=oMOCYbDpT4foe2pN0gsrfAAG1DASm8ak3uUjc9tpCpA,50376
|
98
|
+
sticker_convert-2.8.14.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
99
|
+
sticker_convert-2.8.14.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
|
100
|
+
sticker_convert-2.8.14.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
|
101
|
+
sticker_convert-2.8.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|