sticker-convert 2.1.5__py3-none-any.whl → 2.1.7__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/__init__.py +1 -1
- sticker_convert/__main__.py +7 -4
- sticker_convert/cli.py +42 -32
- sticker_convert/converter.py +432 -0
- sticker_convert/downloaders/download_base.py +40 -16
- sticker_convert/downloaders/download_kakao.py +103 -136
- sticker_convert/downloaders/download_line.py +30 -12
- sticker_convert/downloaders/download_signal.py +48 -32
- sticker_convert/downloaders/download_telegram.py +71 -26
- sticker_convert/gui.py +79 -130
- sticker_convert/{gui_frames → gui_components/frames}/comp_frame.py +2 -3
- sticker_convert/{gui_frames → gui_components/frames}/config_frame.py +3 -4
- sticker_convert/{gui_frames → gui_components/frames}/control_frame.py +2 -2
- sticker_convert/{gui_frames → gui_components/frames}/cred_frame.py +4 -4
- sticker_convert/{gui_frames → gui_components/frames}/input_frame.py +4 -4
- sticker_convert/{gui_frames → gui_components/frames}/output_frame.py +3 -3
- sticker_convert/{gui_frames → gui_components/frames}/progress_frame.py +1 -1
- sticker_convert/{utils → gui_components}/gui_utils.py +38 -21
- sticker_convert/{gui_windows → gui_components/windows}/advanced_compression_window.py +3 -2
- sticker_convert/{gui_windows → gui_components/windows}/base_window.py +3 -2
- sticker_convert/{gui_windows → gui_components/windows}/kakao_get_auth_window.py +3 -3
- sticker_convert/{gui_windows → gui_components/windows}/line_get_auth_window.py +2 -2
- sticker_convert/{gui_windows → gui_components/windows}/signal_get_auth_window.py +2 -2
- sticker_convert/{flow.py → job.py} +91 -102
- sticker_convert/job_option.py +301 -0
- sticker_convert/resources/compression.json +1 -1
- sticker_convert/uploaders/compress_wastickers.py +95 -74
- sticker_convert/uploaders/upload_base.py +16 -4
- sticker_convert/uploaders/upload_signal.py +100 -62
- sticker_convert/uploaders/upload_telegram.py +168 -128
- sticker_convert/uploaders/xcode_imessage.py +202 -132
- sticker_convert/{auth → utils/auth}/get_kakao_auth.py +7 -5
- sticker_convert/{auth → utils/auth}/get_line_auth.py +6 -5
- sticker_convert/{auth → utils/auth}/get_signal_auth.py +1 -1
- sticker_convert/utils/fake_cb_msg.py +5 -2
- sticker_convert/utils/{cache_store.py → files/cache_store.py} +7 -3
- sticker_convert/utils/files/dir_utils.py +64 -0
- sticker_convert/utils/{json_manager.py → files/json_manager.py} +5 -4
- sticker_convert/utils/files/metadata_handler.py +226 -0
- sticker_convert/utils/files/run_bin.py +58 -0
- sticker_convert/utils/{apple_png_normalize.py → media/apple_png_normalize.py} +23 -20
- sticker_convert/utils/{codec_info.py → media/codec_info.py} +41 -35
- sticker_convert/utils/media/decrypt_kakao.py +68 -0
- sticker_convert/utils/media/format_verify.py +184 -0
- sticker_convert/utils/url_detect.py +16 -14
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/METADATA +11 -11
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/RECORD +52 -50
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/WHEEL +1 -1
- sticker_convert/utils/converter.py +0 -399
- sticker_convert/utils/curr_dir.py +0 -70
- sticker_convert/utils/format_verify.py +0 -188
- sticker_convert/utils/metadata_handler.py +0 -190
- sticker_convert/utils/run_bin.py +0 -46
- /sticker_convert/{gui_frames → gui_components/frames}/right_clicker.py +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/LICENSE +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,35 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
from __future__ import annotations
|
2
3
|
from typing import Optional, Union
|
3
4
|
|
4
5
|
import requests
|
5
6
|
|
7
|
+
from ..job_option import CredOption # type: ignore
|
8
|
+
|
9
|
+
|
6
10
|
class DownloadBase:
|
7
|
-
def __init__(
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
url: str,
|
14
|
+
out_dir: str,
|
15
|
+
opt_cred: Optional[CredOption] = None,
|
16
|
+
cb_msg=print,
|
17
|
+
cb_msg_block=input,
|
18
|
+
cb_bar=None,
|
19
|
+
):
|
8
20
|
self.url = url
|
9
21
|
self.out_dir = out_dir
|
10
22
|
self.opt_cred = opt_cred
|
11
23
|
self.cb_msg = cb_msg
|
12
24
|
self.cb_msg_block = cb_msg_block
|
13
25
|
self.cb_bar = cb_bar
|
14
|
-
|
15
|
-
def download_multiple_files(
|
26
|
+
|
27
|
+
def download_multiple_files(
|
28
|
+
self, targets: list[tuple[str, str]], retries: int = 3, **kwargs
|
29
|
+
):
|
16
30
|
# targets format: [(url1, dest2), (url2, dest2), ...]
|
17
31
|
if self.cb_bar:
|
18
|
-
self.cb_bar(set_progress_mode=
|
32
|
+
self.cb_bar(set_progress_mode="determinate", steps=len(targets))
|
19
33
|
|
20
34
|
for url, dest in targets:
|
21
35
|
self.download_file(url, dest, retries, show_progress=False, **kwargs)
|
@@ -23,39 +37,49 @@ class DownloadBase:
|
|
23
37
|
if self.cb_bar:
|
24
38
|
self.cb_bar(update_bar=True)
|
25
39
|
|
26
|
-
def download_file(
|
27
|
-
|
40
|
+
def download_file(
|
41
|
+
self,
|
42
|
+
url: str,
|
43
|
+
dest: Optional[str] = None,
|
44
|
+
retries: int = 3,
|
45
|
+
show_progress: bool = True,
|
46
|
+
**kwargs,
|
47
|
+
) -> Union[bool, bytes]:
|
48
|
+
result = b""
|
28
49
|
chunk_size = 102400
|
29
50
|
|
30
51
|
for retry in range(retries):
|
31
52
|
try:
|
32
53
|
response = requests.get(url, stream=True, **kwargs)
|
33
|
-
total_length = int(response.headers.get(
|
54
|
+
total_length = int(response.headers.get("content-length")) # type: ignore[arg-type]
|
34
55
|
|
35
56
|
if response.status_code != 200:
|
36
57
|
return False
|
37
58
|
else:
|
38
|
-
self.cb_msg(f
|
59
|
+
self.cb_msg(f"Downloading {url}")
|
39
60
|
|
40
61
|
if self.cb_bar and show_progress:
|
41
|
-
self.cb_bar(
|
62
|
+
self.cb_bar(
|
63
|
+
set_progress_mode="determinate",
|
64
|
+
steps=(total_length / chunk_size) + 1,
|
65
|
+
)
|
42
66
|
|
43
67
|
for chunk in response.iter_content(chunk_size=chunk_size):
|
44
68
|
if chunk:
|
45
69
|
result += chunk
|
46
70
|
if self.cb_bar and show_progress:
|
47
71
|
self.cb_bar(update_bar=True)
|
48
|
-
|
72
|
+
|
49
73
|
break
|
50
74
|
except requests.exceptions.RequestException:
|
51
|
-
self.cb_msg(f
|
52
|
-
|
75
|
+
self.cb_msg(f"Cannot download {url} (tried {retry+1}/{retries} times)")
|
76
|
+
|
53
77
|
if not result:
|
54
78
|
return False
|
55
79
|
elif dest:
|
56
|
-
with open(dest,
|
80
|
+
with open(dest, "wb+") as f:
|
57
81
|
f.write(result)
|
58
|
-
self.cb_msg(f
|
82
|
+
self.cb_msg(f"Downloaded {url}")
|
59
83
|
return True
|
60
|
-
else:
|
61
|
-
return result
|
84
|
+
else:
|
85
|
+
return result
|
@@ -1,10 +1,5 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
References:
|
4
|
-
https://github.com/blluv/KakaoTalkEmoticonDownloader
|
5
|
-
https://github.com/star-39/moe-sticker-bot
|
6
|
-
'''
|
7
|
-
|
2
|
+
from __future__ import annotations
|
8
3
|
import requests
|
9
4
|
from bs4 import BeautifulSoup
|
10
5
|
import json
|
@@ -14,146 +9,95 @@ import io
|
|
14
9
|
from urllib.parse import urlparse
|
15
10
|
from typing import Optional
|
16
11
|
|
17
|
-
from .download_base import DownloadBase
|
18
|
-
from ..utils.metadata_handler import MetadataHandler
|
19
|
-
|
20
|
-
|
21
|
-
@staticmethod
|
22
|
-
def generate_lfsr(key: str) -> list[int]:
|
23
|
-
d = list(key*2)
|
24
|
-
seq=[0,0,0]
|
25
|
-
|
26
|
-
seq[0] = 301989938
|
27
|
-
seq[1] = 623357073
|
28
|
-
seq[2] = -2004086252
|
29
|
-
|
30
|
-
i = 0
|
31
|
-
|
32
|
-
for i in range(0, 4):
|
33
|
-
seq[0] = ord(d[i]) | (seq[0] << 8)
|
34
|
-
seq[1] = ord(d[4+i]) | (seq[1] << 8)
|
35
|
-
seq[2] = ord(d[8+i]) | (seq[2] << 8)
|
36
|
-
|
37
|
-
seq[0] = seq[0] & 0xffffffff
|
38
|
-
seq[1] = seq[1] & 0xffffffff
|
39
|
-
seq[2] = seq[2] & 0xffffffff
|
40
|
-
|
41
|
-
return seq
|
12
|
+
from .download_base import DownloadBase # type: ignore
|
13
|
+
from ..utils.files.metadata_handler import MetadataHandler # type: ignore
|
14
|
+
from ..utils.media.decrypt_kakao import DecryptKakao # type: ignore
|
15
|
+
from ..job_option import CredOption # type: ignore
|
42
16
|
|
43
|
-
@staticmethod
|
44
|
-
def xor_byte(b: int, seq: list) -> int:
|
45
|
-
flag1=1
|
46
|
-
flag2=0
|
47
|
-
result=0
|
48
|
-
for _ in range(0, 8):
|
49
|
-
v10 = (seq[0] >> 1)
|
50
|
-
if (seq[0] << 31) & 0xffffffff:
|
51
|
-
seq[0] = (v10 ^ 0xC0000031)
|
52
|
-
v12 = (seq[1] >> 1)
|
53
|
-
if (seq[1] << 31) & 0xffffffff:
|
54
|
-
seq[1] = ((v12 | 0xC0000000) ^ 0x20000010)
|
55
|
-
flag1 = 1
|
56
|
-
else:
|
57
|
-
seq[1] = (v12 & 0x3FFFFFFF)
|
58
|
-
flag1 = 0
|
59
|
-
else:
|
60
|
-
seq[0] = v10
|
61
|
-
v11 = (seq[2] >> 1)
|
62
|
-
if (seq[2] << 31) & 0xffffffff:
|
63
|
-
seq[2] = ((v11 | 0xF0000000) ^ 0x8000001)
|
64
|
-
flag2 = 1
|
65
|
-
else:
|
66
|
-
seq[2] = (v11 & 0xFFFFFFF)
|
67
|
-
flag2 = 0
|
68
|
-
|
69
|
-
result = (flag1 ^ flag2 | 2 * result)
|
70
|
-
return (result ^ b)
|
71
|
-
|
72
|
-
@staticmethod
|
73
|
-
def xor_data(data: bytes) -> bytes:
|
74
|
-
dat = list(data)
|
75
|
-
s = DecryptKakao.generate_lfsr("a271730728cbe141e47fd9d677e9006d")
|
76
|
-
for i in range(0,128):
|
77
|
-
dat[i] = DecryptKakao.xor_byte(dat[i], s)
|
78
|
-
return bytes(dat)
|
79
17
|
|
80
18
|
class MetadataKakao:
|
81
19
|
@staticmethod
|
82
20
|
def get_info_from_share_link(url: str) -> tuple[Optional[str], Optional[str]]:
|
83
|
-
headers = {
|
84
|
-
'User-Agent': 'Android'
|
85
|
-
}
|
21
|
+
headers = {"User-Agent": "Android"}
|
86
22
|
|
87
23
|
response = requests.get(url, headers=headers)
|
88
|
-
soup = BeautifulSoup(response.text,
|
89
|
-
|
90
|
-
pack_title_tag = soup.find(
|
24
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
25
|
+
|
26
|
+
pack_title_tag = soup.find("title")
|
91
27
|
if not pack_title_tag:
|
92
28
|
return None, None
|
93
|
-
|
94
|
-
pack_title = pack_title_tag.string # type: ignore[union-attr]
|
95
29
|
|
96
|
-
|
30
|
+
pack_title = pack_title_tag.string # type: ignore[union-attr]
|
31
|
+
|
32
|
+
data_url = soup.find("a", id="app_scheme_link").get("data-url") # type: ignore[union-attr]
|
97
33
|
if not data_url:
|
98
34
|
return None, None
|
99
|
-
|
100
|
-
item_code = data_url.replace(
|
35
|
+
|
36
|
+
item_code = data_url.replace("kakaotalk://store/emoticon/", "").split("?")[0] # type: ignore[union-attr]
|
101
37
|
|
102
38
|
return pack_title, item_code
|
103
39
|
|
104
40
|
@staticmethod
|
105
|
-
def get_info_from_pack_title(
|
106
|
-
|
41
|
+
def get_info_from_pack_title(
|
42
|
+
pack_title: str,
|
43
|
+
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
44
|
+
pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
|
107
45
|
|
108
46
|
if pack_meta_r.status_code == 200:
|
109
47
|
pack_meta = json.loads(pack_meta_r.text)
|
110
48
|
else:
|
111
49
|
return None, None, None
|
112
50
|
|
113
|
-
author = pack_meta[
|
114
|
-
title_ko = pack_meta[
|
115
|
-
thumbnail_urls = pack_meta[
|
51
|
+
author = pack_meta["result"]["artist"]
|
52
|
+
title_ko = pack_meta["result"]["title"]
|
53
|
+
thumbnail_urls = pack_meta["result"]["thumbnailUrls"]
|
116
54
|
|
117
55
|
return author, title_ko, thumbnail_urls
|
118
56
|
|
119
57
|
@staticmethod
|
120
58
|
def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
|
121
59
|
headers = {
|
122
|
-
|
60
|
+
"Authorization": auth_token,
|
123
61
|
}
|
124
62
|
|
125
|
-
data = {
|
126
|
-
|
127
|
-
|
63
|
+
data = {"query": title_ko}
|
64
|
+
|
65
|
+
response = requests.post(
|
66
|
+
"https://talk-pilsner.kakao.com/emoticon/item_store/instant_search",
|
67
|
+
headers=headers,
|
68
|
+
data=data,
|
69
|
+
)
|
128
70
|
|
129
|
-
response = requests.post('https://talk-pilsner.kakao.com/emoticon/item_store/instant_search', headers=headers, data=data)
|
130
|
-
|
131
71
|
if response.status_code != 200:
|
132
72
|
return None
|
133
73
|
|
134
74
|
response_json = json.loads(response.text)
|
135
|
-
item_code = response_json[
|
75
|
+
item_code = response_json["emoticons"][0]["item_code"]
|
136
76
|
|
137
77
|
return item_code
|
138
78
|
|
139
79
|
@staticmethod
|
140
80
|
def get_title_from_id(item_code: str, auth_token: str) -> Optional[str]:
|
141
81
|
headers = {
|
142
|
-
|
82
|
+
"Authorization": auth_token,
|
143
83
|
}
|
144
84
|
|
145
|
-
response = requests.post(
|
85
|
+
response = requests.post(
|
86
|
+
f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/items/{item_code}",
|
87
|
+
headers=headers,
|
88
|
+
)
|
146
89
|
|
147
90
|
if response.status_code != 200:
|
148
91
|
return None
|
149
|
-
|
92
|
+
|
150
93
|
response_json = json.loads(response.text)
|
151
|
-
title = response_json[
|
94
|
+
title = response_json["itemUnitInfo"][0]["title"]
|
152
95
|
# play_path_format = response_json['itemUnitInfo'][0]['playPathFormat']
|
153
96
|
# stickers_count = len(response_json['itemUnitInfo'][0]['sizes'])
|
154
97
|
|
155
98
|
return title
|
156
99
|
|
100
|
+
|
157
101
|
class DownloadKakao(DownloadBase):
|
158
102
|
def __init__(self, *args, **kwargs):
|
159
103
|
super(DownloadKakao, self).__init__(*args, **kwargs)
|
@@ -162,46 +106,58 @@ class DownloadKakao(DownloadBase):
|
|
162
106
|
|
163
107
|
def download_stickers_kakao(self) -> bool:
|
164
108
|
if self.opt_cred:
|
165
|
-
auth_token = self.opt_cred.
|
109
|
+
auth_token = self.opt_cred.kakao_auth_token
|
166
110
|
|
167
|
-
if urlparse(self.url).netloc ==
|
168
|
-
self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
|
111
|
+
if urlparse(self.url).netloc == "emoticon.kakao.com":
|
112
|
+
self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
|
113
|
+
self.url
|
114
|
+
)
|
169
115
|
|
170
116
|
if item_code:
|
171
117
|
return self.download_animated(item_code)
|
172
118
|
else:
|
173
|
-
self.cb_msg(
|
119
|
+
self.cb_msg(
|
120
|
+
"Download failed: Cannot download metadata for sticker pack"
|
121
|
+
)
|
174
122
|
return False
|
175
123
|
|
176
|
-
elif self.url.isnumeric() or self.url.startswith(
|
177
|
-
item_code = self.url.replace(
|
124
|
+
elif self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
|
125
|
+
item_code = self.url.replace("kakaotalk://store/emoticon/", "")
|
178
126
|
|
179
127
|
self.pack_title = None
|
180
128
|
if auth_token:
|
181
|
-
self.pack_title = MetadataKakao.get_title_from_id(item_code, auth_token)
|
129
|
+
self.pack_title = MetadataKakao.get_title_from_id(item_code, auth_token) # type: ignore[arg-type]
|
182
130
|
if not self.pack_title:
|
183
|
-
self.cb_msg(
|
184
|
-
self.cb_msg(
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
131
|
+
self.cb_msg("Warning: Cannot get pack_title with auth_token.")
|
132
|
+
self.cb_msg(
|
133
|
+
"Is auth_token invalid / expired? Try to regenerate it."
|
134
|
+
)
|
135
|
+
self.cb_msg("Continuing without getting pack_title")
|
136
|
+
|
137
|
+
return self.download_animated(item_code) # type: ignore[arg-type]
|
138
|
+
|
139
|
+
elif urlparse(self.url).netloc == "e.kakao.com":
|
140
|
+
self.pack_title = self.url.replace("https://e.kakao.com/t/", "")
|
141
|
+
(
|
142
|
+
self.author,
|
143
|
+
title_ko,
|
144
|
+
thumbnail_urls,
|
145
|
+
) = MetadataKakao.get_info_from_pack_title(self.pack_title)
|
192
146
|
|
193
147
|
if not thumbnail_urls:
|
194
|
-
self.cb_msg(
|
148
|
+
self.cb_msg(
|
149
|
+
"Download failed: Cannot download metadata for sticker pack"
|
150
|
+
)
|
195
151
|
return False
|
196
152
|
|
197
153
|
if auth_token:
|
198
|
-
item_code = MetadataKakao.get_item_code(title_ko, auth_token)
|
154
|
+
item_code = MetadataKakao.get_item_code(title_ko, auth_token) # type: ignore[arg-type]
|
199
155
|
if item_code:
|
200
156
|
return self.download_animated(item_code)
|
201
157
|
else:
|
202
|
-
msg =
|
203
|
-
msg +=
|
204
|
-
msg +=
|
158
|
+
msg = "Warning: Cannot get item code.\n"
|
159
|
+
msg += "Is auth_token invalid / expired? Try to regenerate it.\n"
|
160
|
+
msg += "Continue to download static stickers instead?"
|
205
161
|
response = self.cb_msg_block(msg)
|
206
162
|
if response == False:
|
207
163
|
return False
|
@@ -209,66 +165,77 @@ class DownloadKakao(DownloadBase):
|
|
209
165
|
return self.download_static(thumbnail_urls)
|
210
166
|
|
211
167
|
else:
|
212
|
-
self.cb_msg(
|
168
|
+
self.cb_msg("Download failed: Unrecognized URL")
|
213
169
|
return False
|
214
170
|
|
215
171
|
def download_static(self, thumbnail_urls: str) -> bool:
|
216
|
-
MetadataHandler.set_metadata(
|
172
|
+
MetadataHandler.set_metadata(
|
173
|
+
self.out_dir, title=self.pack_title, author=self.author
|
174
|
+
)
|
217
175
|
|
218
176
|
targets = []
|
219
177
|
|
220
178
|
num = 0
|
221
179
|
for url in thumbnail_urls:
|
222
|
-
dest = os.path.join(self.out_dir, str(num).zfill(3) +
|
180
|
+
dest = os.path.join(self.out_dir, str(num).zfill(3) + ".png")
|
223
181
|
targets.append((url, dest))
|
224
182
|
num += 1
|
225
183
|
|
226
184
|
self.download_multiple_files(targets)
|
227
|
-
|
185
|
+
|
228
186
|
return True
|
229
|
-
|
187
|
+
|
230
188
|
def download_animated(self, item_code: str) -> bool:
|
231
|
-
MetadataHandler.set_metadata(
|
189
|
+
MetadataHandler.set_metadata(
|
190
|
+
self.out_dir, title=self.pack_title, author=self.author
|
191
|
+
)
|
232
192
|
|
233
193
|
pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
|
234
194
|
|
235
195
|
zip_file = self.download_file(pack_url)
|
236
196
|
if zip_file:
|
237
|
-
self.cb_msg(f
|
197
|
+
self.cb_msg(f"Downloaded {pack_url}")
|
238
198
|
else:
|
239
|
-
self.cb_msg(f
|
199
|
+
self.cb_msg(f"Cannot download {pack_url}")
|
240
200
|
return False
|
241
|
-
|
201
|
+
|
242
202
|
num = 0
|
243
203
|
with zipfile.ZipFile(io.BytesIO(zip_file)) as zf:
|
244
|
-
self.cb_msg(
|
204
|
+
self.cb_msg("Unzipping...")
|
245
205
|
if self.cb_bar:
|
246
|
-
self.cb_bar(set_progress_mode=
|
206
|
+
self.cb_bar(set_progress_mode="determinate", steps=len(zf.namelist()))
|
247
207
|
|
248
208
|
for f_path in sorted(zf.namelist()):
|
249
209
|
_, ext = os.path.splitext(f_path)
|
250
210
|
|
251
|
-
if ext in (
|
211
|
+
if ext in (".gif", ".webp"):
|
252
212
|
data = DecryptKakao.xor_data(zf.read(f_path))
|
253
|
-
self.cb_msg(f
|
213
|
+
self.cb_msg(f"Decrypted {f_path}")
|
254
214
|
else:
|
255
215
|
data = zf.read(f_path)
|
256
|
-
self.cb_msg(f
|
257
|
-
|
216
|
+
self.cb_msg(f"Read {f_path}")
|
217
|
+
|
258
218
|
out_path = os.path.join(self.out_dir, str(num).zfill(3) + ext)
|
259
|
-
with open(out_path,
|
219
|
+
with open(out_path, "wb") as f:
|
260
220
|
f.write(data)
|
261
|
-
|
221
|
+
|
262
222
|
if self.cb_bar:
|
263
223
|
self.cb_bar(update_bar=True)
|
264
224
|
|
265
225
|
num += 1
|
266
226
|
|
267
|
-
self.cb_msg(f
|
268
|
-
|
227
|
+
self.cb_msg(f"Finished getting {pack_url}")
|
228
|
+
|
269
229
|
return True
|
270
230
|
|
271
231
|
@staticmethod
|
272
|
-
def start(
|
232
|
+
def start(
|
233
|
+
url: str,
|
234
|
+
out_dir: str,
|
235
|
+
opt_cred: Optional[CredOption] = None,
|
236
|
+
cb_msg=print,
|
237
|
+
cb_msg_block=input,
|
238
|
+
cb_bar=None,
|
239
|
+
) -> bool:
|
273
240
|
downloader = DownloadKakao(url, out_dir, opt_cred, cb_msg, cb_msg_block, cb_bar)
|
274
|
-
return downloader.download_stickers_kakao()
|
241
|
+
return downloader.download_stickers_kakao()
|
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
from __future__ import annotations
|
2
3
|
'''Reference: https://github.com/doubleplusc/Line-sticker-downloader/blob/master/sticker_dl.py'''
|
3
4
|
|
4
5
|
import requests
|
@@ -13,9 +14,11 @@ from bs4 import BeautifulSoup
|
|
13
14
|
from typing import Optional
|
14
15
|
|
15
16
|
from .download_base import DownloadBase # type: ignore
|
16
|
-
from ..auth.get_line_auth import GetLineAuth # type: ignore
|
17
|
-
from ..utils.metadata_handler import MetadataHandler # type: ignore
|
18
|
-
from ..utils.apple_png_normalize import ApplePngNormalize # type: ignore
|
17
|
+
from ..utils.auth.get_line_auth import GetLineAuth # type: ignore
|
18
|
+
from ..utils.files.metadata_handler import MetadataHandler # type: ignore
|
19
|
+
from ..utils.media.apple_png_normalize import ApplePngNormalize # type: ignore
|
20
|
+
from ..job_option import CredOption # type: ignore
|
21
|
+
|
19
22
|
|
20
23
|
class MetadataLine:
|
21
24
|
@staticmethod
|
@@ -142,17 +145,25 @@ class DownloadLine(DownloadBase):
|
|
142
145
|
|
143
146
|
def load_cookies(self) -> dict[str, str]:
|
144
147
|
cookies = {}
|
145
|
-
if self.opt_cred and self.opt_cred.
|
148
|
+
if self.opt_cred and self.opt_cred.line_cookies:
|
149
|
+
line_cookies = self.opt_cred.line_cookies
|
150
|
+
|
146
151
|
try:
|
147
|
-
|
148
|
-
|
149
|
-
cookies[
|
150
|
-
|
152
|
+
line_cookies_dict = json.loads(line_cookies)
|
153
|
+
for c in line_cookies_dict:
|
154
|
+
cookies[c['name']] = c['value']
|
155
|
+
except json.decoder.JSONDecodeError:
|
156
|
+
try:
|
157
|
+
for i in line_cookies.split(';'):
|
158
|
+
c_key, c_value = i.split('=')
|
159
|
+
cookies[c_key] = c_value
|
160
|
+
except ValueError:
|
151
161
|
self.cb_msg('Warning: Line cookies invalid, you will not be able to add text to "Custom stickers"')
|
152
|
-
cookies = {}
|
153
|
-
except ValueError:
|
154
|
-
self.cb_msg('Warning: Line cookies invalid, you will not be able to add text to "Custom stickers"')
|
155
162
|
|
163
|
+
if not GetLineAuth.validate_cookies(cookies):
|
164
|
+
self.cb_msg('Warning: Line cookies invalid, you will not be able to add text to "Custom stickers"')
|
165
|
+
cookies = {}
|
166
|
+
|
156
167
|
return cookies
|
157
168
|
|
158
169
|
def get_pack_url(self) -> str:
|
@@ -372,6 +383,13 @@ class DownloadLine(DownloadBase):
|
|
372
383
|
return True
|
373
384
|
|
374
385
|
@staticmethod
|
375
|
-
def start(
|
386
|
+
def start(
|
387
|
+
url: str,
|
388
|
+
out_dir: str,
|
389
|
+
opt_cred: Optional[CredOption] = None,
|
390
|
+
cb_msg=print,
|
391
|
+
cb_msg_block=input,
|
392
|
+
cb_bar=None,
|
393
|
+
) -> bool:
|
376
394
|
downloader = DownloadLine(url, out_dir, opt_cred, cb_msg, cb_msg_block, cb_bar)
|
377
395
|
return downloader.download_stickers_line()
|
@@ -3,71 +3,87 @@ import os
|
|
3
3
|
from typing import Optional
|
4
4
|
|
5
5
|
import anyio
|
6
|
-
from signalstickers_client import StickersClient
|
7
|
-
from signalstickers_client.models import StickerPack
|
6
|
+
from signalstickers_client import StickersClient # type: ignore
|
7
|
+
from signalstickers_client.models import StickerPack # type: ignore
|
8
|
+
|
9
|
+
from .download_base import DownloadBase # type: ignore
|
10
|
+
from ..utils.files.metadata_handler import MetadataHandler # type: ignore
|
11
|
+
from ..utils.media.codec_info import CodecInfo # type: ignore
|
12
|
+
from ..job_option import CredOption # type: ignore
|
8
13
|
|
9
|
-
from .download_base import DownloadBase # type: ignore
|
10
|
-
from ..utils.metadata_handler import MetadataHandler # type: ignore
|
11
|
-
from ..utils.codec_info import CodecInfo # type: ignore
|
12
14
|
|
13
15
|
class DownloadSignal(DownloadBase):
|
14
16
|
def __init__(self, *args, **kwargs):
|
15
17
|
super(DownloadSignal, self).__init__(*args, **kwargs)
|
16
|
-
|
18
|
+
|
17
19
|
@staticmethod
|
18
20
|
async def get_pack(pack_id: str, pack_key: str) -> StickerPack:
|
19
21
|
async with StickersClient() as client:
|
20
22
|
pack = await client.get_pack(pack_id, pack_key)
|
21
|
-
|
23
|
+
|
22
24
|
return pack
|
23
25
|
|
24
26
|
def save_stickers(self, pack: StickerPack):
|
25
27
|
if self.cb_bar:
|
26
|
-
self.cb_bar(set_progress_mode=
|
28
|
+
self.cb_bar(set_progress_mode="determinate", steps=len(pack.stickers))
|
27
29
|
|
28
30
|
emoji_dict = {}
|
29
31
|
for sticker in pack.stickers:
|
30
32
|
f_id = str(sticker.id).zfill(3)
|
31
|
-
f_path = os.path.join(self.out_dir, f
|
32
|
-
with open(
|
33
|
+
f_path = os.path.join(self.out_dir, f"{f_id}")
|
34
|
+
with open(
|
35
|
+
f_path,
|
36
|
+
"wb",
|
37
|
+
) as f:
|
33
38
|
f.write(sticker.image_data)
|
34
39
|
|
35
40
|
emoji_dict[f_id] = sticker.emoji
|
36
41
|
|
37
42
|
codec = CodecInfo.get_file_codec(f_path)
|
38
|
-
if
|
39
|
-
f_path_new = f_path +
|
40
|
-
elif
|
41
|
-
f_path_new = f_path +
|
42
|
-
elif
|
43
|
-
f_path_new = f_path +
|
43
|
+
if "apng" in codec:
|
44
|
+
f_path_new = f_path + ".apng"
|
45
|
+
elif "png" in codec:
|
46
|
+
f_path_new = f_path + ".png"
|
47
|
+
elif "webp" in codec:
|
48
|
+
f_path_new = f_path + ".webp"
|
44
49
|
else:
|
45
|
-
self.cb_msg(f
|
46
|
-
codec =
|
47
|
-
f_path_new = f_path +
|
48
|
-
|
50
|
+
self.cb_msg(f"Unknown codec {codec}, defaulting to webp")
|
51
|
+
codec = "webp"
|
52
|
+
f_path_new = f_path + ".webp"
|
53
|
+
|
49
54
|
os.rename(f_path, f_path_new)
|
50
|
-
|
51
|
-
self.cb_msg(f
|
55
|
+
|
56
|
+
self.cb_msg(f"Downloaded {f_id}.{codec}")
|
52
57
|
if self.cb_bar:
|
53
58
|
self.cb_bar(update_bar=True)
|
54
|
-
|
55
|
-
MetadataHandler.set_metadata(
|
59
|
+
|
60
|
+
MetadataHandler.set_metadata(
|
61
|
+
self.out_dir, title=pack.title, author=pack.author, emoji_dict=emoji_dict
|
62
|
+
)
|
56
63
|
|
57
64
|
def download_stickers_signal(self) -> bool:
|
58
|
-
if
|
59
|
-
self.cb_msg(
|
65
|
+
if "signal.art" not in self.url:
|
66
|
+
self.cb_msg("Download failed: Unrecognized URL format")
|
60
67
|
return False
|
61
68
|
|
62
|
-
pack_id = self.url.split(
|
63
|
-
pack_key = self.url.split(
|
69
|
+
pack_id = self.url.split("#pack_id=")[1].split("&pack_key=")[0]
|
70
|
+
pack_key = self.url.split("&pack_key=")[1]
|
64
71
|
|
65
72
|
pack = anyio.run(DownloadSignal.get_pack, pack_id, pack_key)
|
66
73
|
self.save_stickers(pack)
|
67
74
|
|
68
75
|
return True
|
69
|
-
|
76
|
+
|
70
77
|
@staticmethod
|
71
|
-
def start(
|
72
|
-
|
73
|
-
|
78
|
+
def start(
|
79
|
+
url: str,
|
80
|
+
out_dir: str,
|
81
|
+
opt_cred: Optional[CredOption] = None,
|
82
|
+
cb_msg=print,
|
83
|
+
cb_msg_block=input,
|
84
|
+
cb_bar=None,
|
85
|
+
) -> bool:
|
86
|
+
downloader = DownloadSignal(
|
87
|
+
url, out_dir, opt_cred, cb_msg, cb_msg_block, cb_bar
|
88
|
+
)
|
89
|
+
return downloader.download_stickers_signal()
|