sticker-convert 2.13.3.0__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.
- sticker_convert/__main__.py +24 -27
- sticker_convert/auth/__init__.py +0 -0
- sticker_convert/auth/auth_base.py +19 -0
- sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +149 -118
- sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -330
- sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
- sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +281 -263
- sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
- sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +139 -135
- sticker_convert/auth/auth_telethon.py +161 -0
- sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +250 -235
- sticker_convert/{utils/auth → auth}/telegram_api.py +736 -675
- sticker_convert/cli.py +623 -608
- sticker_convert/converter.py +1093 -1084
- sticker_convert/definitions.py +4 -0
- sticker_convert/downloaders/download_band.py +111 -110
- sticker_convert/downloaders/download_base.py +171 -166
- sticker_convert/downloaders/download_discord.py +92 -91
- sticker_convert/downloaders/download_kakao.py +417 -404
- sticker_convert/downloaders/download_line.py +484 -475
- sticker_convert/downloaders/download_ogq.py +80 -79
- sticker_convert/downloaders/download_signal.py +108 -105
- sticker_convert/downloaders/download_telegram.py +56 -55
- sticker_convert/downloaders/download_viber.py +121 -120
- sticker_convert/gui.py +788 -873
- sticker_convert/gui_components/frames/comp_frame.py +180 -166
- sticker_convert/gui_components/frames/config_frame.py +156 -113
- sticker_convert/gui_components/frames/control_frame.py +32 -30
- sticker_convert/gui_components/frames/cred_frame.py +232 -233
- sticker_convert/gui_components/frames/input_frame.py +139 -137
- sticker_convert/gui_components/frames/output_frame.py +112 -110
- sticker_convert/gui_components/frames/right_clicker.py +25 -23
- sticker_convert/gui_components/windows/advanced_compression_window.py +757 -757
- sticker_convert/gui_components/windows/base_window.py +7 -2
- sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -82
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -321
- sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
- sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -89
- sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -168
- sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
- sticker_convert/ios-message-stickers-template/README.md +10 -10
- sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
- sticker_convert/job.py +166 -130
- sticker_convert/job_option.py +1 -0
- sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
- sticker_convert/py.typed +0 -0
- sticker_convert/resources/NotoColorEmoji.ttf +0 -0
- sticker_convert/resources/help.ja_JP.json +88 -0
- sticker_convert/resources/help.json +10 -7
- sticker_convert/resources/help.zh_CN.json +88 -0
- sticker_convert/resources/help.zh_TW.json +88 -0
- sticker_convert/resources/input.ja_JP.json +74 -0
- sticker_convert/resources/input.json +121 -121
- sticker_convert/resources/input.zh_CN.json +74 -0
- sticker_convert/resources/input.zh_TW.json +74 -0
- sticker_convert/resources/output.ja_JP.json +38 -0
- sticker_convert/resources/output.zh_CN.json +38 -0
- sticker_convert/resources/output.zh_TW.json +38 -0
- sticker_convert/uploaders/compress_wastickers.py +186 -177
- sticker_convert/uploaders/upload_base.py +44 -35
- sticker_convert/uploaders/upload_signal.py +218 -203
- sticker_convert/uploaders/upload_telegram.py +353 -338
- sticker_convert/uploaders/upload_viber.py +178 -169
- sticker_convert/uploaders/xcode_imessage.py +295 -286
- sticker_convert/utils/callback.py +238 -6
- sticker_convert/utils/emoji.py +16 -4
- sticker_convert/utils/files/json_resources_loader.py +24 -19
- sticker_convert/utils/files/metadata_handler.py +3 -3
- sticker_convert/utils/translate.py +108 -0
- sticker_convert/utils/url_detect.py +40 -37
- sticker_convert/version.py +1 -1
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +89 -74
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/RECORD +91 -74
- sticker_convert/utils/auth/telethon_setup.py +0 -97
- sticker_convert/utils/singletons.py +0 -18
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/licenses/LICENSE +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
from functools import partial
|
|
3
|
+
from getpass import getpass
|
|
2
4
|
from multiprocessing import Event, Manager
|
|
3
5
|
from multiprocessing.managers import ListProxy, SyncManager
|
|
4
6
|
from queue import Queue
|
|
@@ -19,6 +21,9 @@ if TYPE_CHECKING:
|
|
|
19
21
|
ResponseListType = ListProxy[ResponseItemType] # type: ignore
|
|
20
22
|
CbQueueType = Queue[CbQueueItemType] # type: ignore
|
|
21
23
|
WorkQueueType = Queue[WorkQueueItemType] # type: ignore
|
|
24
|
+
from ttkbootstrap import Toplevel # type: ignore
|
|
25
|
+
|
|
26
|
+
from sticker_convert.gui import GUI # type: ignore
|
|
22
27
|
else:
|
|
23
28
|
ResultsListType = List[Any]
|
|
24
29
|
ResponseListType = List[ResponseItemType]
|
|
@@ -51,12 +56,13 @@ class CallbackProtocol(Protocol):
|
|
|
51
56
|
def put(self, i: Union[CbQueueItemType, str]) -> Any: ...
|
|
52
57
|
|
|
53
58
|
|
|
54
|
-
class
|
|
59
|
+
class CallbackCli(CallbackProtocol):
|
|
55
60
|
def __init__(
|
|
56
61
|
self,
|
|
57
62
|
msg: Optional[Callable[..., None]] = None,
|
|
58
63
|
bar: Optional[Callable[..., None]] = None,
|
|
59
64
|
msg_block: Optional[Callable[..., None]] = None,
|
|
65
|
+
msg_dynamic: Optional[Callable[..., bool]] = None,
|
|
60
66
|
ask_bool: Optional[Callable[..., bool]] = None,
|
|
61
67
|
ask_str: Optional[Callable[..., str]] = None,
|
|
62
68
|
silent: bool = False,
|
|
@@ -80,6 +86,11 @@ class Callback(CallbackProtocol):
|
|
|
80
86
|
else:
|
|
81
87
|
self.msg_block = self.cb_msg_block
|
|
82
88
|
|
|
89
|
+
if msg_dynamic:
|
|
90
|
+
self.msg_dynamic = msg_dynamic
|
|
91
|
+
else:
|
|
92
|
+
self.msg_dynamic = self.cb_msg_dynamic
|
|
93
|
+
|
|
83
94
|
if ask_bool:
|
|
84
95
|
self.ask_bool = ask_bool
|
|
85
96
|
else:
|
|
@@ -136,6 +147,16 @@ class Callback(CallbackProtocol):
|
|
|
136
147
|
if not self.no_confirm:
|
|
137
148
|
input("Press Enter to continue...")
|
|
138
149
|
|
|
150
|
+
def cb_msg_dynamic(self, *args: Any) -> bool:
|
|
151
|
+
message = args[0]
|
|
152
|
+
|
|
153
|
+
if message is None:
|
|
154
|
+
print()
|
|
155
|
+
else:
|
|
156
|
+
print(message, end="\r", flush=True)
|
|
157
|
+
|
|
158
|
+
return True
|
|
159
|
+
|
|
139
160
|
def cb_ask_bool(self, *args: Any, **kwargs: Any) -> bool:
|
|
140
161
|
question = args[0]
|
|
141
162
|
|
|
@@ -158,17 +179,23 @@ class Callback(CallbackProtocol):
|
|
|
158
179
|
|
|
159
180
|
def cb_ask_str(
|
|
160
181
|
self,
|
|
161
|
-
|
|
182
|
+
question: Optional[str] = None,
|
|
162
183
|
initialvalue: Optional[str] = None,
|
|
163
184
|
cli_show_initialvalue: bool = True,
|
|
185
|
+
password: bool = False,
|
|
164
186
|
) -> str:
|
|
165
|
-
self.msg(
|
|
187
|
+
self.msg(question)
|
|
166
188
|
|
|
167
189
|
hint = ""
|
|
168
190
|
if cli_show_initialvalue and initialvalue:
|
|
169
191
|
hint = f" [Default: {initialvalue}]"
|
|
170
192
|
|
|
171
|
-
|
|
193
|
+
if password is True:
|
|
194
|
+
if question is None:
|
|
195
|
+
question = "Password: "
|
|
196
|
+
response = getpass(question)
|
|
197
|
+
else:
|
|
198
|
+
response = input(f"Enter your response and press enter{hint} > ")
|
|
172
199
|
|
|
173
200
|
if initialvalue and not response:
|
|
174
201
|
response = initialvalue
|
|
@@ -179,7 +206,210 @@ class Callback(CallbackProtocol):
|
|
|
179
206
|
if isinstance(i, tuple):
|
|
180
207
|
action = i[0]
|
|
181
208
|
if len(i) >= 2:
|
|
182
|
-
args: Tuple[
|
|
209
|
+
args: Tuple[Any, ...] = i[1] if i[1] else tuple()
|
|
210
|
+
else:
|
|
211
|
+
args = tuple()
|
|
212
|
+
if len(i) >= 3:
|
|
213
|
+
kwargs: Dict[str, Any] = i[2] if i[2] else {}
|
|
214
|
+
else:
|
|
215
|
+
kwargs = {}
|
|
216
|
+
else:
|
|
217
|
+
action = i
|
|
218
|
+
args = tuple()
|
|
219
|
+
kwargs = {}
|
|
220
|
+
|
|
221
|
+
# Fake implementation for Queue.put()
|
|
222
|
+
if action is None:
|
|
223
|
+
return None
|
|
224
|
+
if action == "msg":
|
|
225
|
+
self.msg(*args, **kwargs)
|
|
226
|
+
elif action == "bar":
|
|
227
|
+
self.bar(**kwargs)
|
|
228
|
+
elif action == "update_bar":
|
|
229
|
+
self.bar(update_bar=1)
|
|
230
|
+
elif action == "msg_block":
|
|
231
|
+
return self.msg_block(*args, **kwargs)
|
|
232
|
+
elif action == "msg_dynamic":
|
|
233
|
+
return self.msg_dynamic(*args, **kwargs)
|
|
234
|
+
elif action == "ask_bool":
|
|
235
|
+
return self.ask_bool(*args, **kwargs)
|
|
236
|
+
elif action == "ask_str":
|
|
237
|
+
return self.ask_str(*args, **kwargs)
|
|
238
|
+
else:
|
|
239
|
+
self.msg(action)
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class CallbackGui(CallbackProtocol):
|
|
244
|
+
def __init__(self, gui: "GUI"):
|
|
245
|
+
from ttkbootstrap import Label, Progressbar, StringVar # type: ignore
|
|
246
|
+
|
|
247
|
+
from sticker_convert.gui_components.windows.base_window import BaseWindow
|
|
248
|
+
from sticker_convert.job import Job
|
|
249
|
+
|
|
250
|
+
self.gui = gui
|
|
251
|
+
self.job: Optional[Job] = None
|
|
252
|
+
self.msg_dynamic_window: Optional[BaseWindow] = None
|
|
253
|
+
self.message_var = StringVar(self.gui)
|
|
254
|
+
self.msg_dynamic_label: Optional[Label] = None
|
|
255
|
+
self.msg_dynamic_bar: Optional[Progressbar] = None
|
|
256
|
+
|
|
257
|
+
self.ask_str = self.cb_ask_str
|
|
258
|
+
self.ask_bool = self.cb_ask_bool
|
|
259
|
+
self.msg = self.cb_msg
|
|
260
|
+
self.msg_block = self.cb_msg_block
|
|
261
|
+
self.msg_dynamic = self.cb_msg_dynamic
|
|
262
|
+
self.bar = self.cb_bar
|
|
263
|
+
|
|
264
|
+
def cb_ask_str(
|
|
265
|
+
self,
|
|
266
|
+
question: str,
|
|
267
|
+
initialvalue: Optional[str] = None,
|
|
268
|
+
cli_show_initialvalue: bool = True,
|
|
269
|
+
password: bool = False,
|
|
270
|
+
parent: Optional[object] = None,
|
|
271
|
+
) -> str:
|
|
272
|
+
from ttkbootstrap import Querybox # type: ignore
|
|
273
|
+
|
|
274
|
+
self.gui.action = partial(
|
|
275
|
+
Querybox.get_string, # type: ignore
|
|
276
|
+
question,
|
|
277
|
+
title="sticker-convert",
|
|
278
|
+
initialvalue=initialvalue,
|
|
279
|
+
parent=parent,
|
|
280
|
+
)
|
|
281
|
+
self.gui.event_generate("<<exec_in_main>>")
|
|
282
|
+
self.gui.response_event.wait()
|
|
283
|
+
self.gui.response_event.clear()
|
|
284
|
+
|
|
285
|
+
if self.gui.response is None:
|
|
286
|
+
return ""
|
|
287
|
+
elif isinstance(self.gui.response, str):
|
|
288
|
+
return self.gui.response
|
|
289
|
+
else:
|
|
290
|
+
raise RuntimeError(f"Invalid response in cb_ask_str: {self.gui.response}")
|
|
291
|
+
|
|
292
|
+
def cb_ask_bool(self, question: str, parent: Optional["Toplevel"] = None) -> bool:
|
|
293
|
+
from ttkbootstrap.dialogs import Messagebox # type: ignore
|
|
294
|
+
|
|
295
|
+
self.gui.action = partial(
|
|
296
|
+
Messagebox.yesno, # type: ignore
|
|
297
|
+
question,
|
|
298
|
+
title="sticker-convert",
|
|
299
|
+
parent=parent,
|
|
300
|
+
)
|
|
301
|
+
self.gui.event_generate("<<exec_in_main>>")
|
|
302
|
+
self.gui.response_event.wait()
|
|
303
|
+
self.gui.response_event.clear()
|
|
304
|
+
|
|
305
|
+
if self.gui.response == "Yes":
|
|
306
|
+
return True
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
def cb_msg(self, *args: Any, **kwargs: Any) -> None:
|
|
310
|
+
self.gui.progress_frame.update_message_box(*args, **kwargs)
|
|
311
|
+
|
|
312
|
+
def cb_msg_block(
|
|
313
|
+
self,
|
|
314
|
+
message: Optional[str] = None,
|
|
315
|
+
parent: Optional[object] = None,
|
|
316
|
+
**_kwargs: Any,
|
|
317
|
+
) -> Any:
|
|
318
|
+
from ttkbootstrap.dialogs import Messagebox # type: ignore
|
|
319
|
+
|
|
320
|
+
self.gui.action = partial(
|
|
321
|
+
Messagebox.show_info, # type: ignore
|
|
322
|
+
message,
|
|
323
|
+
title="sticker-convert",
|
|
324
|
+
parent=parent,
|
|
325
|
+
)
|
|
326
|
+
self.gui.event_generate("<<exec_in_main>>")
|
|
327
|
+
self.gui.response_event.wait()
|
|
328
|
+
self.gui.response_event.clear()
|
|
329
|
+
|
|
330
|
+
return self.gui.response
|
|
331
|
+
|
|
332
|
+
def cb_msg_dynamic(
|
|
333
|
+
self,
|
|
334
|
+
message: Optional[str] = None,
|
|
335
|
+
parent: Optional["Toplevel"] = None,
|
|
336
|
+
**_kwargs: Any,
|
|
337
|
+
) -> bool:
|
|
338
|
+
from ttkbootstrap import Label, Progressbar # type: ignore
|
|
339
|
+
|
|
340
|
+
from sticker_convert.gui_components.gui_utils import GUIUtils
|
|
341
|
+
from sticker_convert.gui_components.windows.base_window import BaseWindow
|
|
342
|
+
|
|
343
|
+
self.gui.action = None
|
|
344
|
+
if self.msg_dynamic_window is None:
|
|
345
|
+
if message is not None:
|
|
346
|
+
self.msg_dynamic_window = BaseWindow(self.gui, parent)
|
|
347
|
+
self.msg_dynamic_window.wm_title("sticker-convert")
|
|
348
|
+
self.message_var.set(message)
|
|
349
|
+
self.msg_dynamic_label = Label(
|
|
350
|
+
self.msg_dynamic_window.scrollable_frame,
|
|
351
|
+
textvariable=self.message_var,
|
|
352
|
+
)
|
|
353
|
+
self.msg_dynamic_bar = Progressbar(
|
|
354
|
+
self.msg_dynamic_window.scrollable_frame,
|
|
355
|
+
orient="horizontal",
|
|
356
|
+
mode="indeterminate",
|
|
357
|
+
)
|
|
358
|
+
self.msg_dynamic_bar.start(50)
|
|
359
|
+
self.msg_dynamic_label.pack()
|
|
360
|
+
self.msg_dynamic_bar.pack(fill="x")
|
|
361
|
+
self.gui.action = partial(
|
|
362
|
+
GUIUtils.finalize_window, self.msg_dynamic_window
|
|
363
|
+
)
|
|
364
|
+
print(message, end="\r", flush=True)
|
|
365
|
+
ret = True
|
|
366
|
+
else:
|
|
367
|
+
print()
|
|
368
|
+
ret = False
|
|
369
|
+
elif self.msg_dynamic_window.winfo_exists() == 0:
|
|
370
|
+
self.msg_dynamic_window = None
|
|
371
|
+
print()
|
|
372
|
+
ret = False
|
|
373
|
+
else:
|
|
374
|
+
if message is None:
|
|
375
|
+
|
|
376
|
+
def _action():
|
|
377
|
+
assert self.msg_dynamic_window is not None
|
|
378
|
+
self.msg_dynamic_window.destroy()
|
|
379
|
+
self.msg_dynamic_window = None
|
|
380
|
+
|
|
381
|
+
self.gui.action = _action
|
|
382
|
+
print()
|
|
383
|
+
ret = False
|
|
384
|
+
else:
|
|
385
|
+
self.gui.action = partial(self.message_var.set, message)
|
|
386
|
+
print(message, end="\r", flush=True)
|
|
387
|
+
ret = True
|
|
388
|
+
|
|
389
|
+
if self.gui.action is not None:
|
|
390
|
+
self.gui.event_generate("<<exec_in_main>>")
|
|
391
|
+
self.gui.response_event.wait()
|
|
392
|
+
self.gui.response_event.clear()
|
|
393
|
+
|
|
394
|
+
return ret
|
|
395
|
+
|
|
396
|
+
def cb_bar(
|
|
397
|
+
self,
|
|
398
|
+
set_progress_mode: Optional[str] = None,
|
|
399
|
+
steps: int = 0,
|
|
400
|
+
update_bar: int = 0,
|
|
401
|
+
*args: Any,
|
|
402
|
+
**kwargs: Any,
|
|
403
|
+
) -> None:
|
|
404
|
+
self.gui.progress_frame.update_progress_bar(
|
|
405
|
+
set_progress_mode, steps, update_bar, *args, **kwargs
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def put(self, i: Union[CbQueueItemType, str]) -> Union[str, bool, None]:
|
|
409
|
+
if isinstance(i, tuple):
|
|
410
|
+
action = i[0]
|
|
411
|
+
if len(i) >= 2:
|
|
412
|
+
args: Tuple[Any, ...] = i[1] if i[1] else tuple()
|
|
183
413
|
else:
|
|
184
414
|
args = tuple()
|
|
185
415
|
if len(i) >= 3:
|
|
@@ -202,10 +432,12 @@ class Callback(CallbackProtocol):
|
|
|
202
432
|
self.bar(update_bar=1)
|
|
203
433
|
elif action == "msg_block":
|
|
204
434
|
return self.msg_block(*args, **kwargs)
|
|
435
|
+
elif action == "msg_dynamic":
|
|
436
|
+
return self.msg_dynamic(*args, **kwargs)
|
|
205
437
|
elif action == "ask_bool":
|
|
206
438
|
return self.ask_bool(*args, **kwargs)
|
|
207
439
|
elif action == "ask_str":
|
|
208
|
-
return self.ask_str(**kwargs)
|
|
440
|
+
return self.ask_str(*args, **kwargs)
|
|
209
441
|
else:
|
|
210
442
|
self.msg(action)
|
|
211
443
|
return None
|
sticker_convert/utils/emoji.py
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import Callable, List, Sequence, Set
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from ugrapheme import graphemes # type: ignore
|
|
5
|
+
|
|
6
|
+
from sticker_convert.utils.files.json_resources_loader import load_resource_json
|
|
7
|
+
|
|
8
|
+
graphemes: Callable[[str], Sequence[str]] # type: ignore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# https://stackoverflow.com/a/480227
|
|
12
|
+
# Return list of unique items of list while preserve order
|
|
13
|
+
def uniques(seq: List[str]):
|
|
14
|
+
seen: Set[str] = set()
|
|
15
|
+
seen_add = seen.add
|
|
16
|
+
return [x for x in seq if not (x in seen or seen_add(x))]
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
def get_emoji_list() -> List[str]:
|
|
8
|
-
return [i["emoji"] for i in
|
|
20
|
+
return [i["emoji"] for i in load_resource_json("emoji")]
|
|
9
21
|
|
|
10
22
|
|
|
11
23
|
EMOJI_LIST = get_emoji_list()
|
|
@@ -13,4 +25,4 @@ EMOJI_LIST = get_emoji_list()
|
|
|
13
25
|
|
|
14
26
|
# https://stackoverflow.com/a/43146653
|
|
15
27
|
def extract_emojis(s: str) -> str:
|
|
16
|
-
return "".join(
|
|
28
|
+
return "".join(uniques(list(c for c in graphemes(s) if c in EMOJI_LIST)))
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from typing import Any, Dict, cast
|
|
2
3
|
|
|
3
4
|
from mergedeep import merge # type: ignore
|
|
4
5
|
|
|
5
|
-
from sticker_convert.definitions import CONFIG_DIR, ROOT_DIR
|
|
6
|
+
from sticker_convert.definitions import CONFIG_DIR, ROOT_DIR, RUNTIME_STATE
|
|
6
7
|
from sticker_convert.utils.files.json_manager import JsonManager
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
compression_json: Dict[Any, Any] = merge( # type: ignore
|
|
15
|
-
compression_json, # type: ignore
|
|
16
|
-
custom_preset_json,
|
|
17
|
-
)
|
|
18
|
-
return compression_json
|
|
10
|
+
def load_resource_json(json_name: str) -> Dict[Any, Any]:
|
|
11
|
+
if RUNTIME_STATE.get(f"{json_name}_json") is not None:
|
|
12
|
+
return cast(Dict[Any, Any], RUNTIME_STATE[f"{json_name}_json"])
|
|
13
|
+
else:
|
|
14
|
+
loaded_json = JsonManager.load_json(ROOT_DIR / f"resources/{json_name}.json")
|
|
19
15
|
|
|
16
|
+
json_to_merge = None
|
|
17
|
+
lang = RUNTIME_STATE["LANG"]
|
|
18
|
+
if lang != "en_US":
|
|
19
|
+
# Translated json
|
|
20
|
+
json_to_merge = ROOT_DIR / f"resources/{json_name}.{lang}.json"
|
|
21
|
+
if json_name == "compression":
|
|
22
|
+
# Custom preset json
|
|
23
|
+
json_to_merge = CONFIG_DIR / "custom_preset.json"
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
if json_to_merge and json_to_merge.exists():
|
|
26
|
+
custom_preset_json = JsonManager.load_json(json_to_merge)
|
|
27
|
+
loaded_json: Dict[Any, Any] = merge( # type: ignore
|
|
28
|
+
loaded_json, # type: ignore
|
|
29
|
+
custom_preset_json,
|
|
30
|
+
)
|
|
31
|
+
RUNTIME_STATE[f"{json_name}_json"] = loaded_json
|
|
32
|
+
return loaded_json
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Dict, List, Optional, Tuple
|
|
7
7
|
|
|
8
|
-
from sticker_convert.utils.files.json_resources_loader import
|
|
8
|
+
from sticker_convert.utils.files.json_resources_loader import load_resource_json
|
|
9
9
|
from sticker_convert.utils.media.codec_info import CodecInfo
|
|
10
10
|
|
|
11
11
|
RELATED_EXTENSIONS = (
|
|
@@ -166,7 +166,7 @@ class MetadataHandler:
|
|
|
166
166
|
Does not check if metadata provided via user input in GUI or flag options
|
|
167
167
|
metadata = 'title' or 'author'
|
|
168
168
|
"""
|
|
169
|
-
input_presets =
|
|
169
|
+
input_presets = load_resource_json("input")
|
|
170
170
|
assert input_presets
|
|
171
171
|
|
|
172
172
|
if input_option == "local":
|
|
@@ -185,7 +185,7 @@ class MetadataHandler:
|
|
|
185
185
|
@staticmethod
|
|
186
186
|
def check_metadata_required(output_option: str, metadata: str) -> bool:
|
|
187
187
|
# metadata = 'title' or 'author'
|
|
188
|
-
output_presets =
|
|
188
|
+
output_presets = load_resource_json("output")
|
|
189
189
|
assert output_presets
|
|
190
190
|
return output_presets[output_option]["metadata_requirements"][metadata]
|
|
191
191
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import gettext
|
|
3
|
+
import locale
|
|
4
|
+
import platform
|
|
5
|
+
import sys
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
from multiprocessing import current_process
|
|
8
|
+
from typing import Any, Callable, Dict, cast
|
|
9
|
+
|
|
10
|
+
from sticker_convert.definitions import CONFIG_DIR, ROOT_DIR, RUNTIME_STATE
|
|
11
|
+
from sticker_convert.utils.files.json_manager import JsonManager
|
|
12
|
+
|
|
13
|
+
LANG_DICT = {
|
|
14
|
+
"en_US": ("en_US",),
|
|
15
|
+
"ja_JP": ("ja_jp",),
|
|
16
|
+
"zh_CN": ("zh_chs", "zh_cn", "zh_sg"),
|
|
17
|
+
"zh_TW": ("zh_tw", "zh_hk", "zh_mo", "zh_cht"),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_default_lang() -> str:
|
|
22
|
+
lang = locale.getlocale()[0]
|
|
23
|
+
winlang = ""
|
|
24
|
+
if platform.system() == "Windows":
|
|
25
|
+
import ctypes
|
|
26
|
+
|
|
27
|
+
windll = ctypes.windll.kernel32 # type: ignore
|
|
28
|
+
winlang_id = cast(int, windll.GetUserDefaultUILanguage()) # type: ignore
|
|
29
|
+
winlang = cast(str, locale.windows_locale[winlang_id]) # type: ignore
|
|
30
|
+
|
|
31
|
+
for k, v in LANG_DICT.items():
|
|
32
|
+
for i in v:
|
|
33
|
+
if (
|
|
34
|
+
lang is not None and lang.lower().startswith(i)
|
|
35
|
+
) or winlang.lower().startswith(i):
|
|
36
|
+
return k
|
|
37
|
+
|
|
38
|
+
return "en_US"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_lang() -> str:
|
|
42
|
+
# Priority 1: From --lang CLI flag
|
|
43
|
+
is_lang = False
|
|
44
|
+
lang = None
|
|
45
|
+
for i in sys.argv:
|
|
46
|
+
if is_lang:
|
|
47
|
+
return i
|
|
48
|
+
if i == "--lang":
|
|
49
|
+
is_lang = True
|
|
50
|
+
|
|
51
|
+
# Priority 2: From config
|
|
52
|
+
settings_path = CONFIG_DIR / "config.json"
|
|
53
|
+
if settings_path.is_file():
|
|
54
|
+
try:
|
|
55
|
+
settings: Dict[Any, Any] = JsonManager.load_json(settings_path)
|
|
56
|
+
lang = settings.get("lang")
|
|
57
|
+
if lang:
|
|
58
|
+
if lang in LANG_DICT:
|
|
59
|
+
return lang
|
|
60
|
+
elif lang != "auto":
|
|
61
|
+
return "en_US"
|
|
62
|
+
except JSONDecodeError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
# Priority 3: Default locale
|
|
66
|
+
lang = get_default_lang()
|
|
67
|
+
return lang
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
LANG = RUNTIME_STATE.get("LANG", get_lang())
|
|
71
|
+
RUNTIME_STATE["LANG"] = LANG
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def gettext_dummy(x: str) -> str:
|
|
75
|
+
return x
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_translator() -> Callable[[str], str]:
|
|
79
|
+
if RUNTIME_STATE.get("TRANS") is not None:
|
|
80
|
+
return RUNTIME_STATE["TRANS"]
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
trans: Callable[[str], str] = gettext.translation(
|
|
84
|
+
"base", ROOT_DIR / "locales", languages=[RUNTIME_STATE["LANG"]]
|
|
85
|
+
).gettext
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
if current_process().name == "MainProcess":
|
|
88
|
+
print(f"Warning: Cannot find locales/{LANG}/LC_MESSAGES/base.mo")
|
|
89
|
+
print(
|
|
90
|
+
"Please generate by running scripts/update-locales.sh in root of repo"
|
|
91
|
+
)
|
|
92
|
+
print("Fallback to using English...")
|
|
93
|
+
trans = gettext_dummy
|
|
94
|
+
RUNTIME_STATE["TRANS"] = trans
|
|
95
|
+
return trans
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def I(x: str) -> str: # noqa: E743
|
|
99
|
+
return get_translator()(x)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
SUPPORTED_LANG = {
|
|
103
|
+
I("Auto"): "auto",
|
|
104
|
+
"English": "en_US",
|
|
105
|
+
"繁體中文": "zh_TW",
|
|
106
|
+
"简体中文": "zh_CN",
|
|
107
|
+
"日本語": "ja_JP",
|
|
108
|
+
}
|
|
@@ -1,37 +1,40 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import string
|
|
3
|
-
from typing import Optional
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class UrlDetect:
|
|
8
|
-
@staticmethod
|
|
9
|
-
def detect(url: str) -> Optional[str]:
|
|
10
|
-
domain = urlparse(url).netloc
|
|
11
|
-
|
|
12
|
-
if domain == "signal.art" or url.startswith("sgnl://addstickers/"):
|
|
13
|
-
return "signal"
|
|
14
|
-
|
|
15
|
-
if domain in ("telegram.me", "t.me"):
|
|
16
|
-
return "telegram"
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
domain in ("store.line.me", "line.me")
|
|
20
|
-
or url.startswith("line://shop/detail/")
|
|
21
|
-
or (len(url) == 24 and all(c in string.hexdigits for c in url))
|
|
22
|
-
):
|
|
23
|
-
return "line"
|
|
24
|
-
|
|
25
|
-
if domain in ("e.kakao.com", "emoticon.kakao.com"):
|
|
26
|
-
return "kakao"
|
|
27
|
-
|
|
28
|
-
if domain == "www.band.us":
|
|
29
|
-
return "band"
|
|
30
|
-
|
|
31
|
-
if domain == "
|
|
32
|
-
return "
|
|
33
|
-
|
|
34
|
-
if domain == "
|
|
35
|
-
return "
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import string
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UrlDetect:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def detect(url: str) -> Optional[str]:
|
|
10
|
+
domain = urlparse(url).netloc
|
|
11
|
+
|
|
12
|
+
if domain == "signal.art" or url.startswith("sgnl://addstickers/"):
|
|
13
|
+
return "signal"
|
|
14
|
+
|
|
15
|
+
if domain in ("telegram.me", "t.me"):
|
|
16
|
+
return "telegram"
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
domain in ("store.line.me", "line.me")
|
|
20
|
+
or url.startswith("line://shop/detail/")
|
|
21
|
+
or (len(url) == 24 and all(c in string.hexdigits for c in url))
|
|
22
|
+
):
|
|
23
|
+
return "line"
|
|
24
|
+
|
|
25
|
+
if domain in ("e.kakao.com", "emoticon.kakao.com"):
|
|
26
|
+
return "kakao"
|
|
27
|
+
|
|
28
|
+
if domain == "www.band.us":
|
|
29
|
+
return "band"
|
|
30
|
+
|
|
31
|
+
if domain == "ogqmarket.naver.com":
|
|
32
|
+
return "ogq"
|
|
33
|
+
|
|
34
|
+
if domain == "stickers.viber.com":
|
|
35
|
+
return "viber"
|
|
36
|
+
|
|
37
|
+
if domain == "discord.com":
|
|
38
|
+
return "discord"
|
|
39
|
+
|
|
40
|
+
return None
|
sticker_convert/version.py
CHANGED