sticker-convert 2.8.11__py3-none-any.whl → 2.8.13__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 +200 -49
- sticker_convert/version.py +1 -1
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/METADATA +5 -4
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/RECORD +8 -8
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/LICENSE +0 -0
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/WHEEL +0 -0
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.8.11.dist-info → sticker_convert-2.8.13.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,15 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
|
+
import itertools
|
4
5
|
import json
|
5
6
|
import zipfile
|
6
7
|
from io import BytesIO
|
7
8
|
from pathlib import Path
|
8
|
-
from typing import Any, List, Optional, Tuple
|
9
|
+
from typing import Any, List, Optional, Tuple, cast
|
9
10
|
from urllib.parse import urlparse
|
10
11
|
|
12
|
+
import js2py # type: ignore
|
11
13
|
import requests
|
12
14
|
from bs4 import BeautifulSoup
|
13
15
|
from bs4.element import Tag
|
@@ -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,35 +58,34 @@ 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
|
-
|
61
|
+
item_code_fake = cast(str, app_scheme_link_tag["data-i"])
|
62
|
+
|
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:
|
41
69
|
return None, None
|
42
|
-
if isinstance(data_urls, list):
|
43
|
-
data_url = data_urls[0]
|
44
|
-
else:
|
45
|
-
data_url = data_urls
|
46
70
|
|
47
|
-
|
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[: func_end_pos + 1]
|
76
|
+
web2app_start_pos = js.find("daumtools.web2app(")
|
77
|
+
js = js[:web2app_start_pos] + "return a;}"
|
78
|
+
get_item_code = js2py.eval_js(js) # type: ignore
|
79
|
+
kakao_scheme_link = cast(
|
80
|
+
str,
|
81
|
+
get_item_code(
|
82
|
+
"kakaotalk://store/emoticon/${i}?referer=share_link", item_code_fake
|
83
|
+
),
|
84
|
+
)
|
85
|
+
item_code = urlparse(kakao_scheme_link).path.split("/")[-1]
|
48
86
|
|
49
87
|
return pack_title, item_code
|
50
88
|
|
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
89
|
@staticmethod
|
69
90
|
def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
|
70
91
|
headers = {
|
@@ -88,9 +109,27 @@ class MetadataKakao:
|
|
88
109
|
return item_code
|
89
110
|
|
90
111
|
@staticmethod
|
91
|
-
def
|
112
|
+
def get_pack_info_unauthed(
|
113
|
+
pack_title: str,
|
114
|
+
) -> Optional[dict[str, Any]]:
|
115
|
+
pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
|
116
|
+
|
117
|
+
if pack_meta_r.status_code == 200:
|
118
|
+
pack_meta = json.loads(pack_meta_r.text)
|
119
|
+
else:
|
120
|
+
return None
|
121
|
+
|
122
|
+
return pack_meta
|
123
|
+
|
124
|
+
@staticmethod
|
125
|
+
def get_pack_info_authed(
|
126
|
+
item_code: str, auth_token: str
|
127
|
+
) -> Optional[dict[str, Any]]:
|
92
128
|
headers = {
|
93
129
|
"Authorization": auth_token,
|
130
|
+
"Talk-Agent": "android/10.8.1",
|
131
|
+
"Talk-Language": "en",
|
132
|
+
"User-Agent": "okhttp/4.10.0",
|
94
133
|
}
|
95
134
|
|
96
135
|
response = requests.post(
|
@@ -102,11 +141,8 @@ class MetadataKakao:
|
|
102
141
|
return None
|
103
142
|
|
104
143
|
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
144
|
|
109
|
-
return
|
145
|
+
return response_json
|
110
146
|
|
111
147
|
|
112
148
|
class DownloadKakao(DownloadBase):
|
@@ -114,11 +150,15 @@ class DownloadKakao(DownloadBase):
|
|
114
150
|
super().__init__(*args, **kwargs)
|
115
151
|
self.pack_title: Optional[str] = None
|
116
152
|
self.author: Optional[str] = None
|
153
|
+
self.auth_token: Optional[str] = None
|
154
|
+
|
155
|
+
self.pack_info_unauthed: Optional[dict[str, Any]] = None
|
156
|
+
self.pack_info_authed: Optional[dict[str, Any]] = None
|
117
157
|
|
118
158
|
def download_stickers_kakao(self) -> bool:
|
119
|
-
auth_token = None
|
159
|
+
self.auth_token = None
|
120
160
|
if self.opt_cred:
|
121
|
-
auth_token = self.opt_cred.kakao_auth_token
|
161
|
+
self.auth_token = self.opt_cred.kakao_auth_token
|
122
162
|
|
123
163
|
if urlparse(self.url).netloc == "emoticon.kakao.com":
|
124
164
|
self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
|
@@ -134,9 +174,13 @@ class DownloadKakao(DownloadBase):
|
|
134
174
|
item_code = self.url.replace("kakaotalk://store/emoticon/", "")
|
135
175
|
|
136
176
|
self.pack_title = None
|
137
|
-
if auth_token:
|
138
|
-
self.
|
139
|
-
|
177
|
+
if self.auth_token:
|
178
|
+
self.pack_info_authed = MetadataKakao.get_pack_info_authed(
|
179
|
+
item_code, self.auth_token
|
180
|
+
)
|
181
|
+
if self.pack_info_authed:
|
182
|
+
self.pack_title = self.pack_info_authed["itemUnitInfo"][0]["title"]
|
183
|
+
else:
|
140
184
|
self.cb.put("Warning: Cannot get pack_title with auth_token.")
|
141
185
|
self.cb.put(
|
142
186
|
"Is auth_token invalid / expired? Try to regenerate it."
|
@@ -146,25 +190,23 @@ class DownloadKakao(DownloadBase):
|
|
146
190
|
return self.download_animated(item_code)
|
147
191
|
|
148
192
|
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:
|
193
|
+
self.pack_title = urlparse(self.url).path.split("/")[-1]
|
194
|
+
self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
|
195
|
+
self.pack_title
|
196
|
+
)
|
197
|
+
|
198
|
+
if not self.pack_info_unauthed:
|
161
199
|
self.cb.put(
|
162
200
|
"Download failed: Cannot download metadata for sticker pack"
|
163
201
|
)
|
164
202
|
return False
|
165
203
|
|
166
|
-
|
167
|
-
|
204
|
+
self.author = self.pack_info_unauthed["result"]["artist"]
|
205
|
+
title_ko = self.pack_info_unauthed["result"]["title"]
|
206
|
+
thumbnail_urls = self.pack_info_unauthed["result"]["thumbnailUrls"]
|
207
|
+
|
208
|
+
if self.auth_token:
|
209
|
+
item_code = MetadataKakao.get_item_code(title_ko, self.auth_token)
|
168
210
|
if item_code:
|
169
211
|
return self.download_animated(item_code)
|
170
212
|
msg = "Warning: Cannot get item code.\n"
|
@@ -204,6 +246,115 @@ class DownloadKakao(DownloadBase):
|
|
204
246
|
self.out_dir, title=self.pack_title, author=self.author
|
205
247
|
)
|
206
248
|
|
249
|
+
success = self.download_animated_zip(item_code)
|
250
|
+
if not success:
|
251
|
+
self.cb.put("Trying to download one by one")
|
252
|
+
success = self.download_animated_files(item_code)
|
253
|
+
|
254
|
+
return success
|
255
|
+
|
256
|
+
def download_animated_files(self, item_code: str) -> bool:
|
257
|
+
play_exts = [".webp", ".gif", ".png", ""]
|
258
|
+
play_types = ["emot", "emoji", ""] # emot = normal; emoji = mini
|
259
|
+
play_path_format = None
|
260
|
+
sound_exts = [".mp3", ""]
|
261
|
+
sound_path_format = None
|
262
|
+
stickers_count = 32 # https://emoticonstudio.kakao.com/pages/start
|
263
|
+
|
264
|
+
if not self.pack_info_authed and self.auth_token:
|
265
|
+
self.pack_info_authed = MetadataKakao.get_pack_info_authed(
|
266
|
+
item_code, self.auth_token
|
267
|
+
)
|
268
|
+
if self.pack_info_authed:
|
269
|
+
preview_data = self.pack_info_authed["itemUnitInfo"][0]["previewData"]
|
270
|
+
play_path_format = preview_data["playPathFormat"]
|
271
|
+
sound_path_format = preview_data["soundPathFormat"]
|
272
|
+
stickers_count = preview_data["num"]
|
273
|
+
else:
|
274
|
+
if not self.pack_info_unauthed:
|
275
|
+
public_url = None
|
276
|
+
if urlparse(self.url).netloc == "emoticon.kakao.com":
|
277
|
+
r = requests.get(self.url)
|
278
|
+
# Share url would redirect to public url without headers
|
279
|
+
public_url = r.url
|
280
|
+
elif urlparse(self.url).netloc == "e.kakao.com":
|
281
|
+
public_url = self.url
|
282
|
+
if public_url:
|
283
|
+
pack_title = urlparse(public_url).path.split("/")[-1]
|
284
|
+
self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
|
285
|
+
pack_title
|
286
|
+
)
|
287
|
+
|
288
|
+
if self.pack_info_unauthed:
|
289
|
+
stickers_count = len(self.pack_info_unauthed["result"]["thumbnailUrls"])
|
290
|
+
|
291
|
+
play_type = ""
|
292
|
+
play_ext = ""
|
293
|
+
if play_path_format is None:
|
294
|
+
for play_type, play_ext in itertools.product(play_types, play_exts):
|
295
|
+
r = requests.get(
|
296
|
+
f"https://item.kakaocdn.net/dw/{item_code}.{play_type}_001{play_ext}"
|
297
|
+
)
|
298
|
+
if r.ok:
|
299
|
+
break
|
300
|
+
if play_ext == "":
|
301
|
+
self.cb.put(f"Failed to determine extension of {item_code}")
|
302
|
+
return False
|
303
|
+
else:
|
304
|
+
play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
|
305
|
+
else:
|
306
|
+
play_ext = "." + play_path_format.split(".")[-1]
|
307
|
+
|
308
|
+
sound_ext = ""
|
309
|
+
if sound_path_format is None:
|
310
|
+
for sound_ext in sound_exts:
|
311
|
+
r = requests.get(
|
312
|
+
f"https://item.kakaocdn.net/dw/{item_code}.sound_001{sound_ext}"
|
313
|
+
)
|
314
|
+
if r.ok:
|
315
|
+
break
|
316
|
+
if sound_ext != "":
|
317
|
+
sound_path_format = f"dw/{item_code}.sound_0##{sound_ext}"
|
318
|
+
elif sound_path_format != "":
|
319
|
+
sound_ext = "." + sound_path_format.split(".")[-1]
|
320
|
+
|
321
|
+
assert play_path_format
|
322
|
+
targets: list[tuple[str, Path]] = []
|
323
|
+
for num in range(1, stickers_count + 1):
|
324
|
+
play_url = "https://item.kakaocdn.net/" + play_path_format.replace(
|
325
|
+
"##", str(num).zfill(2)
|
326
|
+
)
|
327
|
+
play_dl_path = Path(self.out_dir, str(num).zfill(3) + play_ext)
|
328
|
+
targets.append((play_url, play_dl_path))
|
329
|
+
|
330
|
+
if sound_path_format:
|
331
|
+
sound_url = "https://item.kakaocdn.net/" + sound_path_format.replace(
|
332
|
+
"##", str(num).zfill(2)
|
333
|
+
)
|
334
|
+
sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
|
335
|
+
targets.append((sound_url, sound_dl_path))
|
336
|
+
|
337
|
+
self.download_multiple_files(targets)
|
338
|
+
|
339
|
+
for target in targets:
|
340
|
+
f_path = target[1]
|
341
|
+
ext = Path(f_path).suffix
|
342
|
+
|
343
|
+
if ext not in (".gif", ".webp"):
|
344
|
+
continue
|
345
|
+
|
346
|
+
with open(f_path, "rb") as f:
|
347
|
+
data = f.read()
|
348
|
+
data = DecryptKakao.xor_data(data)
|
349
|
+
self.cb.put(f"Decrypted {f_path}")
|
350
|
+
with open(f_path, "wb+") as f:
|
351
|
+
f.write(data)
|
352
|
+
|
353
|
+
self.cb.put(f"Finished getting {item_code}")
|
354
|
+
|
355
|
+
return True
|
356
|
+
|
357
|
+
def download_animated_zip(self, item_code: str) -> bool:
|
207
358
|
pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
|
208
359
|
|
209
360
|
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.13
|
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>
|
@@ -371,15 +371,16 @@ Requires-Dist: av ~=12.1.0
|
|
371
371
|
Requires-Dist: beautifulsoup4 ~=4.12.3
|
372
372
|
Requires-Dist: rookiepy ~=0.5.1
|
373
373
|
Requires-Dist: imagequant ~=1.1.1
|
374
|
+
Requires-Dist: js2py ~=0.74
|
374
375
|
Requires-Dist: memory-tempfile ~=2.2.3
|
375
376
|
Requires-Dist: mergedeep ~=1.3.4
|
376
377
|
Requires-Dist: numpy >=1.22.4
|
377
378
|
Requires-Dist: Pillow ~=10.3.0
|
378
379
|
Requires-Dist: pyoxipng ~=9.0.0
|
379
380
|
Requires-Dist: python-telegram-bot ~=21.2
|
380
|
-
Requires-Dist: requests ~=2.32.
|
381
|
-
Requires-Dist: rlottie-python ~=1.3.
|
382
|
-
Requires-Dist: signalstickers-client-fork-laggykiller ~=3.3.0.
|
381
|
+
Requires-Dist: requests ~=2.32.3
|
382
|
+
Requires-Dist: rlottie-python ~=1.3.5
|
383
|
+
Requires-Dist: signalstickers-client-fork-laggykiller ~=3.3.0.post2
|
383
384
|
Requires-Dist: sqlcipher3-wheels ~=0.5.2.post1
|
384
385
|
Requires-Dist: tqdm ~=4.66.4
|
385
386
|
Requires-Dist: ttkbootstrap-fork-laggykiller ~=1.5.1
|
@@ -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=9-pwqpZSBU0RJFweDIiL0yO5EXYfWSFjeU-Fw26RIA4,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=d0950G_Nqpb9cCWgVKVJBtSNFSykv5zrye0uqHAGenY,14193
|
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.13.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
97
|
+
sticker_convert-2.8.13.dist-info/METADATA,sha256=7GSocwPMQW299PlQZ9BHiA5LGu-Je1_UfMyh3uBbZ-Q,50404
|
98
|
+
sticker_convert-2.8.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
99
|
+
sticker_convert-2.8.13.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
|
100
|
+
sticker_convert-2.8.13.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
|
101
|
+
sticker_convert-2.8.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|