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.
Files changed (93) hide show
  1. sticker_convert/__main__.py +24 -27
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +149 -118
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -330
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +281 -263
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +139 -135
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +250 -235
  12. sticker_convert/{utils/auth → auth}/telegram_api.py +736 -675
  13. sticker_convert/cli.py +623 -608
  14. sticker_convert/converter.py +1093 -1084
  15. sticker_convert/definitions.py +4 -0
  16. sticker_convert/downloaders/download_band.py +111 -110
  17. sticker_convert/downloaders/download_base.py +171 -166
  18. sticker_convert/downloaders/download_discord.py +92 -91
  19. sticker_convert/downloaders/download_kakao.py +417 -404
  20. sticker_convert/downloaders/download_line.py +484 -475
  21. sticker_convert/downloaders/download_ogq.py +80 -79
  22. sticker_convert/downloaders/download_signal.py +108 -105
  23. sticker_convert/downloaders/download_telegram.py +56 -55
  24. sticker_convert/downloaders/download_viber.py +121 -120
  25. sticker_convert/gui.py +788 -873
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -166
  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 -233
  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 -757
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -82
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -321
  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 -89
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -168
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/README.md +10 -10
  42. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  43. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  50. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  51. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  52. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  53. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  54. sticker_convert/job.py +166 -130
  55. sticker_convert/job_option.py +1 -0
  56. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  57. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  58. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  59. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  60. sticker_convert/py.typed +0 -0
  61. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  62. sticker_convert/resources/help.ja_JP.json +88 -0
  63. sticker_convert/resources/help.json +10 -7
  64. sticker_convert/resources/help.zh_CN.json +88 -0
  65. sticker_convert/resources/help.zh_TW.json +88 -0
  66. sticker_convert/resources/input.ja_JP.json +74 -0
  67. sticker_convert/resources/input.json +121 -121
  68. sticker_convert/resources/input.zh_CN.json +74 -0
  69. sticker_convert/resources/input.zh_TW.json +74 -0
  70. sticker_convert/resources/output.ja_JP.json +38 -0
  71. sticker_convert/resources/output.zh_CN.json +38 -0
  72. sticker_convert/resources/output.zh_TW.json +38 -0
  73. sticker_convert/uploaders/compress_wastickers.py +186 -177
  74. sticker_convert/uploaders/upload_base.py +44 -35
  75. sticker_convert/uploaders/upload_signal.py +218 -203
  76. sticker_convert/uploaders/upload_telegram.py +353 -338
  77. sticker_convert/uploaders/upload_viber.py +178 -169
  78. sticker_convert/uploaders/xcode_imessage.py +295 -286
  79. sticker_convert/utils/callback.py +238 -6
  80. sticker_convert/utils/emoji.py +16 -4
  81. sticker_convert/utils/files/json_resources_loader.py +24 -19
  82. sticker_convert/utils/files/metadata_handler.py +3 -3
  83. sticker_convert/utils/translate.py +108 -0
  84. sticker_convert/utils/url_detect.py +40 -37
  85. sticker_convert/version.py +1 -1
  86. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +89 -74
  87. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/RECORD +91 -74
  88. sticker_convert/utils/auth/telethon_setup.py +0 -97
  89. sticker_convert/utils/singletons.py +0 -18
  90. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +0 -0
  91. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  92. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/licenses/LICENSE +0 -0
  93. {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 Callback(CallbackProtocol):
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
- msg: Optional[str] = None,
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(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
- response = input(f"Enter your response and press enter{hint} > ")
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[str, ...] = i[1] if i[1] else 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
@@ -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 sticker_convert.utils.files.json_resources_loader import EMOJI_JSON
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 EMOJI_JSON]
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(set(c for c in s if c in EMOJI_LIST))
28
+ return "".join(uniques(list(c for c in graphemes(s) if c in EMOJI_LIST)))
@@ -1,27 +1,32 @@
1
- from typing import Any, Dict
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 _load_compression() -> Dict[Any, Any]:
10
- compression_json = JsonManager.load_json(ROOT_DIR / "resources/compression.json")
11
- custom_preset_json_path = CONFIG_DIR / "custom_preset.json"
12
- if custom_preset_json_path.exists():
13
- custom_preset_json = JsonManager.load_json(custom_preset_json_path)
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
- HELP_JSON: Dict[str, Dict[str, str]] = JsonManager.load_json(
22
- ROOT_DIR / "resources/help.json"
23
- )
24
- INPUT_JSON = JsonManager.load_json(ROOT_DIR / "resources/input.json")
25
- COMPRESSION_JSON = _load_compression()
26
- OUTPUT_JSON = JsonManager.load_json(ROOT_DIR / "resources/output.json")
27
- EMOJI_JSON = JsonManager.load_json(ROOT_DIR / "resources/emoji.json")
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 INPUT_JSON, OUTPUT_JSON
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 = INPUT_JSON
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 = OUTPUT_JSON
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 == "stickers.viber.com":
32
- return "viber"
33
-
34
- if domain == "discord.com":
35
- return "discord"
36
-
37
- return None
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
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.13.3.0"
3
+ __version__ = "2.17.0.0"