sticker-convert 2.8.12__tar.gz → 2.8.14__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. {sticker_convert-2.8.12/src/sticker_convert.egg-info → sticker_convert-2.8.14}/PKG-INFO +3 -3
  2. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/requirements.txt +2 -2
  3. sticker_convert-2.8.14/src/sticker_convert/downloaders/download_kakao.py +428 -0
  4. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/version.py +1 -1
  5. {sticker_convert-2.8.12 → sticker_convert-2.8.14/src/sticker_convert.egg-info}/PKG-INFO +3 -3
  6. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert.egg-info/requires.txt +2 -2
  7. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/tests/test_download.py +14 -0
  8. sticker_convert-2.8.12/src/sticker_convert/downloaders/download_kakao.py +0 -255
  9. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/LICENSE +0 -0
  10. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/MANIFEST.in +0 -0
  11. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/README.md +0 -0
  12. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/pyproject.toml +0 -0
  13. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/setup.cfg +0 -0
  14. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/__init__.py +0 -0
  15. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/__main__.py +0 -0
  16. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/cli.py +0 -0
  17. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/converter.py +0 -0
  18. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/definitions.py +0 -0
  19. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/__init__.py +0 -0
  20. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/download_base.py +0 -0
  21. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/download_line.py +0 -0
  22. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/download_signal.py +0 -0
  23. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/download_telegram.py +0 -0
  24. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/downloaders/download_viber.py +0 -0
  25. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui.py +0 -0
  26. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/__init__.py +0 -0
  27. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/__init__.py +0 -0
  28. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/comp_frame.py +0 -0
  29. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/config_frame.py +0 -0
  30. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/control_frame.py +0 -0
  31. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/cred_frame.py +0 -0
  32. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/input_frame.py +0 -0
  33. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/output_frame.py +0 -0
  34. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/progress_frame.py +0 -0
  35. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/frames/right_clicker.py +0 -0
  36. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/gui_utils.py +0 -0
  37. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/__init__.py +0 -0
  38. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/advanced_compression_window.py +0 -0
  39. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/base_window.py +0 -0
  40. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/kakao_get_auth_window.py +0 -0
  41. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/line_get_auth_window.py +0 -0
  42. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/gui_components/windows/signal_get_auth_window.py +0 -0
  43. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +0 -0
  44. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  45. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/README.md +0 -0
  46. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers/Info.plist +0 -0
  47. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +0 -0
  48. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +0 -0
  49. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +0 -0
  50. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +0 -0
  51. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  52. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +0 -0
  53. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  54. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +0 -0
  55. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  56. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  57. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +0 -0
  58. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  59. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  60. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  61. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  62. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  63. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  64. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  65. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  66. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  67. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  68. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  69. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  70. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +0 -0
  71. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  72. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  73. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  74. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +0 -0
  75. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/job.py +0 -0
  76. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/job_option.py +0 -0
  77. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  78. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/appicon.icns +0 -0
  79. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/appicon.ico +0 -0
  80. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/appicon.png +0 -0
  81. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/compression.json +0 -0
  82. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/emoji.json +0 -0
  83. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/help.json +0 -0
  84. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/input.json +0 -0
  85. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/resources/output.json +0 -0
  86. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/__init__.py +0 -0
  87. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/compress_wastickers.py +0 -0
  88. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/upload_base.py +0 -0
  89. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/upload_signal.py +0 -0
  90. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/upload_telegram.py +0 -0
  91. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/uploaders/xcode_imessage.py +0 -0
  92. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/auth/get_kakao_auth.py +0 -0
  93. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/auth/get_line_auth.py +0 -0
  94. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/auth/get_signal_auth.py +0 -0
  95. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/callback.py +0 -0
  96. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/cache_store.py +0 -0
  97. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/json_manager.py +0 -0
  98. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/json_resources_loader.py +0 -0
  99. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/metadata_handler.py +0 -0
  100. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/run_bin.py +0 -0
  101. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/files/sanitize_filename.py +0 -0
  102. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/media/apple_png_normalize.py +0 -0
  103. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/media/codec_info.py +0 -0
  104. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/media/decrypt_kakao.py +0 -0
  105. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/media/format_verify.py +0 -0
  106. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert/utils/url_detect.py +0 -0
  107. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert.egg-info/SOURCES.txt +0 -0
  108. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert.egg-info/dependency_links.txt +0 -0
  109. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert.egg-info/entry_points.txt +0 -0
  110. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/src/sticker_convert.egg-info/top_level.txt +0 -0
  111. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/tests/test_compression.py +0 -0
  112. {sticker_convert-2.8.12 → sticker_convert-2.8.14}/tests/test_export.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sticker-convert
3
- Version: 2.8.12
3
+ Version: 2.8.14
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -377,8 +377,8 @@ Requires-Dist: numpy>=1.22.4
377
377
  Requires-Dist: Pillow~=10.3.0
378
378
  Requires-Dist: pyoxipng~=9.0.0
379
379
  Requires-Dist: python-telegram-bot~=21.2
380
- Requires-Dist: requests~=2.32.2
381
- Requires-Dist: rlottie_python~=1.3.4
380
+ Requires-Dist: requests~=2.32.3
381
+ Requires-Dist: rlottie_python~=1.3.5
382
382
  Requires-Dist: signalstickers-client-fork-laggykiller~=3.3.0.post2
383
383
  Requires-Dist: sqlcipher3-wheels~=0.5.2.post1
384
384
  Requires-Dist: tqdm~=4.66.4
@@ -11,8 +11,8 @@ numpy>=1.22.4
11
11
  Pillow~=10.3.0
12
12
  pyoxipng~=9.0.0
13
13
  python-telegram-bot~=21.2
14
- requests~=2.32.2
15
- rlottie_python~=1.3.4
14
+ requests~=2.32.3
15
+ rlottie_python~=1.3.5
16
16
  signalstickers-client-fork-laggykiller~=3.3.0.post2
17
17
  sqlcipher3-wheels~=0.5.2.post1
18
18
  tqdm~=4.66.4
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import itertools
5
+ import json
6
+ import re
7
+ import zipfile
8
+ from io import BytesIO
9
+ from pathlib import Path
10
+ from typing import Any, List, Optional, Tuple, cast
11
+ from urllib.parse import urlparse
12
+
13
+ import requests
14
+ from bs4 import BeautifulSoup
15
+ from bs4.element import Tag
16
+
17
+ from sticker_convert.downloaders.download_base import DownloadBase
18
+ from sticker_convert.job_option import CredOption
19
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
20
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
21
+ from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
22
+
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
+
44
+ class MetadataKakao:
45
+ @staticmethod
46
+ def get_info_from_share_link(url: str) -> Tuple[Optional[str], Optional[str]]:
47
+ headers = {"User-Agent": "Android"}
48
+
49
+ response = requests.get(url, headers=headers)
50
+ soup = BeautifulSoup(response.text, "html.parser")
51
+
52
+ pack_title_tag = soup.find("title") # type: ignore
53
+ if not pack_title_tag:
54
+ return None, None
55
+
56
+ pack_title: str = pack_title_tag.string # type: ignore
57
+
58
+ app_scheme_link_tag = soup.find("a", id="app_scheme_link") # type: ignore
59
+ assert isinstance(app_scheme_link_tag, Tag)
60
+
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:
69
+ return None, None
70
+
71
+ func_start_pos = js.find("function emoticonDeepLink(")
72
+ js = js[func_start_pos:]
73
+ bracket_start_pos = js.find("{")
74
+ func_end_pos = search_bracket(js[bracket_start_pos:]) + bracket_start_pos
75
+ js = js[bracket_start_pos + 1 : func_end_pos]
76
+ js = js.split(";")[0]
77
+
78
+ minus_num_regex = re.search(r"\-(.*?)\^", js)
79
+ if not minus_num_regex:
80
+ return None, None
81
+ minus_num_str = minus_num_regex.group(1)
82
+ if not minus_num_str.isnumeric():
83
+ return None, None
84
+ minus_num = int(minus_num_str)
85
+
86
+ xor_num_regex = re.search(r"\^(.*?)\)", js)
87
+ if not xor_num_regex:
88
+ return None, None
89
+ xor_num_str = xor_num_regex.group(1)
90
+ if not xor_num_str.isnumeric():
91
+ return None, None
92
+ xor_num = int(xor_num_str)
93
+
94
+ item_code = str(int(item_code_fake) - minus_num ^ xor_num)
95
+
96
+ # https://github.com/Nuitka/Nuitka/issues/385
97
+ # js2py not working if compiled by nuitka
98
+ # web2app_start_pos = js.find("daumtools.web2app(")
99
+ # js = js[:web2app_start_pos] + "return a;}"
100
+ # get_item_code = js2py.eval_js(js) # type: ignore
101
+ # kakao_scheme_link = cast(
102
+ # str,
103
+ # get_item_code(
104
+ # "kakaotalk://store/emoticon/${i}?referer=share_link", item_code_fake
105
+ # ),
106
+ # )
107
+ # item_code = urlparse(kakao_scheme_link).path.split("/")[-1]
108
+
109
+ return pack_title, item_code
110
+
111
+ @staticmethod
112
+ def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
113
+ headers = {
114
+ "Authorization": auth_token,
115
+ }
116
+
117
+ data = {"query": title_ko}
118
+
119
+ response = requests.post(
120
+ "https://talk-pilsner.kakao.com/emoticon/item_store/instant_search",
121
+ headers=headers,
122
+ data=data,
123
+ )
124
+
125
+ if response.status_code != 200:
126
+ return None
127
+
128
+ response_json = json.loads(response.text)
129
+ item_code = response_json["emoticons"][0]["item_code"]
130
+
131
+ return item_code
132
+
133
+ @staticmethod
134
+ def get_pack_info_unauthed(
135
+ pack_title: str,
136
+ ) -> Optional[dict[str, Any]]:
137
+ pack_meta_r = requests.get(f"https://e.kakao.com/api/v1/items/t/{pack_title}")
138
+
139
+ if pack_meta_r.status_code == 200:
140
+ pack_meta = json.loads(pack_meta_r.text)
141
+ else:
142
+ return None
143
+
144
+ return pack_meta
145
+
146
+ @staticmethod
147
+ def get_pack_info_authed(
148
+ item_code: str, auth_token: str
149
+ ) -> Optional[dict[str, Any]]:
150
+ headers = {
151
+ "Authorization": auth_token,
152
+ "Talk-Agent": "android/10.8.1",
153
+ "Talk-Language": "en",
154
+ "User-Agent": "okhttp/4.10.0",
155
+ }
156
+
157
+ response = requests.post(
158
+ f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/items/{item_code}",
159
+ headers=headers,
160
+ )
161
+
162
+ if response.status_code != 200:
163
+ return None
164
+
165
+ response_json = json.loads(response.text)
166
+
167
+ return response_json
168
+
169
+
170
+ class DownloadKakao(DownloadBase):
171
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
172
+ super().__init__(*args, **kwargs)
173
+ self.pack_title: Optional[str] = None
174
+ self.author: Optional[str] = None
175
+ self.auth_token: Optional[str] = None
176
+
177
+ self.pack_info_unauthed: Optional[dict[str, Any]] = None
178
+ self.pack_info_authed: Optional[dict[str, Any]] = None
179
+
180
+ def download_stickers_kakao(self) -> bool:
181
+ self.auth_token = None
182
+ if self.opt_cred:
183
+ self.auth_token = self.opt_cred.kakao_auth_token
184
+
185
+ if urlparse(self.url).netloc == "emoticon.kakao.com":
186
+ self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
187
+ self.url
188
+ )
189
+
190
+ if item_code:
191
+ return self.download_animated(item_code)
192
+ self.cb.put("Download failed: Cannot download metadata for sticker pack")
193
+ return False
194
+
195
+ if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
196
+ item_code = self.url.replace("kakaotalk://store/emoticon/", "")
197
+
198
+ self.pack_title = None
199
+ if self.auth_token:
200
+ self.pack_info_authed = MetadataKakao.get_pack_info_authed(
201
+ item_code, self.auth_token
202
+ )
203
+ if self.pack_info_authed:
204
+ self.pack_title = self.pack_info_authed["itemUnitInfo"][0]["title"]
205
+ else:
206
+ self.cb.put("Warning: Cannot get pack_title with auth_token.")
207
+ self.cb.put(
208
+ "Is auth_token invalid / expired? Try to regenerate it."
209
+ )
210
+ self.cb.put("Continuing without getting pack_title")
211
+
212
+ return self.download_animated(item_code)
213
+
214
+ if urlparse(self.url).netloc == "e.kakao.com":
215
+ self.pack_title = urlparse(self.url).path.split("/")[-1]
216
+ self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
217
+ self.pack_title
218
+ )
219
+
220
+ if not self.pack_info_unauthed:
221
+ self.cb.put(
222
+ "Download failed: Cannot download metadata for sticker pack"
223
+ )
224
+ return False
225
+
226
+ self.author = self.pack_info_unauthed["result"]["artist"]
227
+ title_ko = self.pack_info_unauthed["result"]["title"]
228
+ thumbnail_urls = self.pack_info_unauthed["result"]["thumbnailUrls"]
229
+
230
+ if self.auth_token:
231
+ item_code = MetadataKakao.get_item_code(title_ko, self.auth_token)
232
+ if item_code:
233
+ return self.download_animated(item_code)
234
+ msg = "Warning: Cannot get item code.\n"
235
+ msg += "Is auth_token invalid / expired? Try to regenerate it.\n"
236
+ msg += "Continue to download static stickers instead?"
237
+ self.cb.put(("ask_bool", (msg,), None))
238
+ if self.cb_return:
239
+ response = self.cb_return.get_response()
240
+ else:
241
+ response = False
242
+
243
+ if response is False:
244
+ return False
245
+
246
+ return self.download_static(thumbnail_urls)
247
+
248
+ self.cb.put("Download failed: Unrecognized URL")
249
+ return False
250
+
251
+ def download_static(self, thumbnail_urls: str) -> bool:
252
+ MetadataHandler.set_metadata(
253
+ self.out_dir, title=self.pack_title, author=self.author
254
+ )
255
+
256
+ targets: List[Tuple[str, Path]] = []
257
+
258
+ for num, url in enumerate(thumbnail_urls):
259
+ dest = Path(self.out_dir, str(num).zfill(3) + ".png")
260
+ targets.append((url, dest))
261
+
262
+ self.download_multiple_files(targets)
263
+
264
+ return True
265
+
266
+ def download_animated(self, item_code: str) -> bool:
267
+ MetadataHandler.set_metadata(
268
+ self.out_dir, title=self.pack_title, author=self.author
269
+ )
270
+
271
+ success = self.download_animated_zip(item_code)
272
+ if not success:
273
+ self.cb.put("Trying to download one by one")
274
+ success = self.download_animated_files(item_code)
275
+
276
+ return success
277
+
278
+ def download_animated_files(self, item_code: str) -> bool:
279
+ play_exts = [".webp", ".gif", ".png", ""]
280
+ play_types = ["emot", "emoji", ""] # emot = normal; emoji = mini
281
+ play_path_format = None
282
+ sound_exts = [".mp3", ""]
283
+ sound_path_format = None
284
+ stickers_count = 32 # https://emoticonstudio.kakao.com/pages/start
285
+
286
+ if not self.pack_info_authed and self.auth_token:
287
+ self.pack_info_authed = MetadataKakao.get_pack_info_authed(
288
+ item_code, self.auth_token
289
+ )
290
+ if self.pack_info_authed:
291
+ preview_data = self.pack_info_authed["itemUnitInfo"][0]["previewData"]
292
+ play_path_format = preview_data["playPathFormat"]
293
+ sound_path_format = preview_data["soundPathFormat"]
294
+ stickers_count = preview_data["num"]
295
+ else:
296
+ if not self.pack_info_unauthed:
297
+ public_url = None
298
+ if urlparse(self.url).netloc == "emoticon.kakao.com":
299
+ r = requests.get(self.url)
300
+ # Share url would redirect to public url without headers
301
+ public_url = r.url
302
+ elif urlparse(self.url).netloc == "e.kakao.com":
303
+ public_url = self.url
304
+ if public_url:
305
+ pack_title = urlparse(public_url).path.split("/")[-1]
306
+ self.pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(
307
+ pack_title
308
+ )
309
+
310
+ if self.pack_info_unauthed:
311
+ stickers_count = len(self.pack_info_unauthed["result"]["thumbnailUrls"])
312
+
313
+ play_type = ""
314
+ play_ext = ""
315
+ if play_path_format is None:
316
+ for play_type, play_ext in itertools.product(play_types, play_exts):
317
+ r = requests.get(
318
+ f"https://item.kakaocdn.net/dw/{item_code}.{play_type}_001{play_ext}"
319
+ )
320
+ if r.ok:
321
+ break
322
+ if play_ext == "":
323
+ self.cb.put(f"Failed to determine extension of {item_code}")
324
+ return False
325
+ else:
326
+ play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
327
+ else:
328
+ play_ext = "." + play_path_format.split(".")[-1]
329
+
330
+ sound_ext = ""
331
+ if sound_path_format is None:
332
+ for sound_ext in sound_exts:
333
+ r = requests.get(
334
+ f"https://item.kakaocdn.net/dw/{item_code}.sound_001{sound_ext}"
335
+ )
336
+ if r.ok:
337
+ break
338
+ if sound_ext != "":
339
+ sound_path_format = f"dw/{item_code}.sound_0##{sound_ext}"
340
+ elif sound_path_format != "":
341
+ sound_ext = "." + sound_path_format.split(".")[-1]
342
+
343
+ assert play_path_format
344
+ targets: list[tuple[str, Path]] = []
345
+ for num in range(1, stickers_count + 1):
346
+ play_url = "https://item.kakaocdn.net/" + play_path_format.replace(
347
+ "##", str(num).zfill(2)
348
+ )
349
+ play_dl_path = Path(self.out_dir, str(num).zfill(3) + play_ext)
350
+ targets.append((play_url, play_dl_path))
351
+
352
+ if sound_path_format:
353
+ sound_url = "https://item.kakaocdn.net/" + sound_path_format.replace(
354
+ "##", str(num).zfill(2)
355
+ )
356
+ sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
357
+ targets.append((sound_url, sound_dl_path))
358
+
359
+ self.download_multiple_files(targets)
360
+
361
+ for target in targets:
362
+ f_path = target[1]
363
+ ext = Path(f_path).suffix
364
+
365
+ if ext not in (".gif", ".webp"):
366
+ continue
367
+
368
+ with open(f_path, "rb") as f:
369
+ data = f.read()
370
+ data = DecryptKakao.xor_data(data)
371
+ self.cb.put(f"Decrypted {f_path}")
372
+ with open(f_path, "wb+") as f:
373
+ f.write(data)
374
+
375
+ self.cb.put(f"Finished getting {item_code}")
376
+
377
+ return True
378
+
379
+ def download_animated_zip(self, item_code: str) -> bool:
380
+ pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
381
+
382
+ zip_file = self.download_file(pack_url)
383
+ if zip_file:
384
+ self.cb.put(f"Downloaded {pack_url}")
385
+ else:
386
+ self.cb.put(f"Cannot download {pack_url}")
387
+ return False
388
+
389
+ with zipfile.ZipFile(BytesIO(zip_file)) as zf:
390
+ self.cb.put("Unzipping...")
391
+ self.cb.put(
392
+ (
393
+ "bar",
394
+ None,
395
+ {"set_progress_mode": "determinate", "steps": len(zf.namelist())},
396
+ )
397
+ )
398
+
399
+ for num, f_path in enumerate(sorted(zf.namelist())):
400
+ ext = Path(f_path).suffix
401
+
402
+ if ext in (".gif", ".webp"):
403
+ data = DecryptKakao.xor_data(zf.read(f_path))
404
+ self.cb.put(f"Decrypted {f_path}")
405
+ else:
406
+ data = zf.read(f_path)
407
+ self.cb.put(f"Read {f_path}")
408
+
409
+ out_path = Path(self.out_dir, str(num).zfill(3) + ext)
410
+ with open(out_path, "wb") as f:
411
+ f.write(data)
412
+
413
+ self.cb.put("update_bar")
414
+
415
+ self.cb.put(f"Finished getting {pack_url}")
416
+
417
+ return True
418
+
419
+ @staticmethod
420
+ def start(
421
+ url: str,
422
+ out_dir: Path,
423
+ opt_cred: Optional[CredOption],
424
+ cb: CallbackProtocol,
425
+ cb_return: CallbackReturn,
426
+ ) -> bool:
427
+ downloader = DownloadKakao(url, out_dir, opt_cred, cb, cb_return)
428
+ return downloader.download_stickers_kakao()
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.8.12"
3
+ __version__ = "2.8.14"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sticker-convert
3
- Version: 2.8.12
3
+ Version: 2.8.14
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -377,8 +377,8 @@ Requires-Dist: numpy>=1.22.4
377
377
  Requires-Dist: Pillow~=10.3.0
378
378
  Requires-Dist: pyoxipng~=9.0.0
379
379
  Requires-Dist: python-telegram-bot~=21.2
380
- Requires-Dist: requests~=2.32.2
381
- Requires-Dist: rlottie_python~=1.3.4
380
+ Requires-Dist: requests~=2.32.3
381
+ Requires-Dist: rlottie_python~=1.3.5
382
382
  Requires-Dist: signalstickers-client-fork-laggykiller~=3.3.0.post2
383
383
  Requires-Dist: sqlcipher3-wheels~=0.5.2.post1
384
384
  Requires-Dist: tqdm~=4.66.4
@@ -11,8 +11,8 @@ numpy>=1.22.4
11
11
  Pillow~=10.3.0
12
12
  pyoxipng~=9.0.0
13
13
  python-telegram-bot~=21.2
14
- requests~=2.32.2
15
- rlottie_python~=1.3.4
14
+ requests~=2.32.3
15
+ rlottie_python~=1.3.5
16
16
  signalstickers-client-fork-laggykiller~=3.3.0.post2
17
17
  sqlcipher3-wheels~=0.5.2.post1
18
18
  tqdm~=4.66.4
@@ -414,6 +414,20 @@ def test_download_kakao_animated_gif_share_link(tmp_path: LocalPath) -> None:
414
414
  )
415
415
 
416
416
 
417
+ @pytest.mark.skipif(not TEST_DOWNLOAD, reason="TEST_DOWNLOAD not set")
418
+ def test_download_kakao_mini_share_link(tmp_path: LocalPath) -> None:
419
+ _run_sticker_convert(
420
+ tmp_path=tmp_path,
421
+ source="kakao",
422
+ url="https://emoticon.kakao.com/items/EL7hYM1hlS0X-9LuBH_9fOc3xNg=?lang=ko&referer=share_link",
423
+ expected_file_count=35,
424
+ expected_file_formats=[".webp"],
425
+ with_title=True,
426
+ with_author=True,
427
+ with_emoji=False,
428
+ )
429
+
430
+
417
431
  @pytest.mark.skipif(not TEST_DOWNLOAD, reason="TEST_DOWNLOAD not set")
418
432
  def test_download_viber_custom_sticker_packs(tmp_path: LocalPath) -> None:
419
433
  _run_sticker_convert(
@@ -1,255 +0,0 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import json
5
- import zipfile
6
- from io import BytesIO
7
- from pathlib import Path
8
- from typing import Any, List, Optional, Tuple
9
- from urllib.parse import urlparse
10
-
11
- import requests
12
- from bs4 import BeautifulSoup
13
- from bs4.element import Tag
14
-
15
- from sticker_convert.downloaders.download_base import DownloadBase
16
- from sticker_convert.job_option import CredOption
17
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
18
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
19
- from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
20
-
21
-
22
- class MetadataKakao:
23
- @staticmethod
24
- def get_info_from_share_link(url: str) -> Tuple[Optional[str], Optional[str]]:
25
- headers = {"User-Agent": "Android"}
26
-
27
- response = requests.get(url, headers=headers)
28
- soup = BeautifulSoup(response.text, "html.parser")
29
-
30
- pack_title_tag = soup.find("title") # type: ignore
31
- if not pack_title_tag:
32
- return None, None
33
-
34
- pack_title: str = pack_title_tag.string # type: ignore
35
-
36
- app_scheme_link_tag = soup.find("a", id="app_scheme_link") # type: ignore
37
- assert isinstance(app_scheme_link_tag, Tag)
38
-
39
- data_urls = app_scheme_link_tag.get("data-url")
40
- if not data_urls:
41
- return None, None
42
- if isinstance(data_urls, list):
43
- data_url = data_urls[0]
44
- else:
45
- data_url = data_urls
46
-
47
- item_code = data_url.replace("kakaotalk://store/emoticon/", "").split("?")[0]
48
-
49
- return pack_title, item_code
50
-
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
- @staticmethod
69
- def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
70
- headers = {
71
- "Authorization": auth_token,
72
- }
73
-
74
- data = {"query": title_ko}
75
-
76
- response = requests.post(
77
- "https://talk-pilsner.kakao.com/emoticon/item_store/instant_search",
78
- headers=headers,
79
- data=data,
80
- )
81
-
82
- if response.status_code != 200:
83
- return None
84
-
85
- response_json = json.loads(response.text)
86
- item_code = response_json["emoticons"][0]["item_code"]
87
-
88
- return item_code
89
-
90
- @staticmethod
91
- def get_title_from_id(item_code: str, auth_token: str) -> Optional[str]:
92
- headers = {
93
- "Authorization": auth_token,
94
- }
95
-
96
- response = requests.post(
97
- f"https://talk-pilsner.kakao.com/emoticon/api/store/v3/items/{item_code}",
98
- headers=headers,
99
- )
100
-
101
- if response.status_code != 200:
102
- return None
103
-
104
- 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
-
109
- return title
110
-
111
-
112
- class DownloadKakao(DownloadBase):
113
- def __init__(self, *args: Any, **kwargs: Any) -> None:
114
- super().__init__(*args, **kwargs)
115
- self.pack_title: Optional[str] = None
116
- self.author: Optional[str] = None
117
-
118
- def download_stickers_kakao(self) -> bool:
119
- auth_token = None
120
- if self.opt_cred:
121
- auth_token = self.opt_cred.kakao_auth_token
122
-
123
- if urlparse(self.url).netloc == "emoticon.kakao.com":
124
- self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
125
- self.url
126
- )
127
-
128
- if item_code:
129
- return self.download_animated(item_code)
130
- self.cb.put("Download failed: Cannot download metadata for sticker pack")
131
- return False
132
-
133
- if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
134
- item_code = self.url.replace("kakaotalk://store/emoticon/", "")
135
-
136
- self.pack_title = None
137
- if auth_token:
138
- self.pack_title = MetadataKakao.get_title_from_id(item_code, auth_token)
139
- if not self.pack_title:
140
- self.cb.put("Warning: Cannot get pack_title with auth_token.")
141
- self.cb.put(
142
- "Is auth_token invalid / expired? Try to regenerate it."
143
- )
144
- self.cb.put("Continuing without getting pack_title")
145
-
146
- return self.download_animated(item_code)
147
-
148
- if urlparse(self.url).netloc == "e.kakao.com":
149
- self.pack_title = self.url.replace("https://e.kakao.com/t/", "")
150
- (
151
- self.author,
152
- title_ko,
153
- thumbnail_urls,
154
- ) = MetadataKakao.get_info_from_pack_title(self.pack_title)
155
-
156
- assert self.author
157
- assert title_ko
158
- assert thumbnail_urls
159
-
160
- if not thumbnail_urls:
161
- self.cb.put(
162
- "Download failed: Cannot download metadata for sticker pack"
163
- )
164
- return False
165
-
166
- if auth_token:
167
- item_code = MetadataKakao.get_item_code(title_ko, auth_token)
168
- if item_code:
169
- return self.download_animated(item_code)
170
- msg = "Warning: Cannot get item code.\n"
171
- msg += "Is auth_token invalid / expired? Try to regenerate it.\n"
172
- msg += "Continue to download static stickers instead?"
173
- self.cb.put(("ask_bool", (msg,), None))
174
- if self.cb_return:
175
- response = self.cb_return.get_response()
176
- else:
177
- response = False
178
-
179
- if response is False:
180
- return False
181
-
182
- return self.download_static(thumbnail_urls)
183
-
184
- self.cb.put("Download failed: Unrecognized URL")
185
- return False
186
-
187
- def download_static(self, thumbnail_urls: str) -> bool:
188
- MetadataHandler.set_metadata(
189
- self.out_dir, title=self.pack_title, author=self.author
190
- )
191
-
192
- targets: List[Tuple[str, Path]] = []
193
-
194
- for num, url in enumerate(thumbnail_urls):
195
- dest = Path(self.out_dir, str(num).zfill(3) + ".png")
196
- targets.append((url, dest))
197
-
198
- self.download_multiple_files(targets)
199
-
200
- return True
201
-
202
- def download_animated(self, item_code: str) -> bool:
203
- MetadataHandler.set_metadata(
204
- self.out_dir, title=self.pack_title, author=self.author
205
- )
206
-
207
- pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
208
-
209
- zip_file = self.download_file(pack_url)
210
- if zip_file:
211
- self.cb.put(f"Downloaded {pack_url}")
212
- else:
213
- self.cb.put(f"Cannot download {pack_url}")
214
- return False
215
-
216
- with zipfile.ZipFile(BytesIO(zip_file)) as zf:
217
- self.cb.put("Unzipping...")
218
- self.cb.put(
219
- (
220
- "bar",
221
- None,
222
- {"set_progress_mode": "determinate", "steps": len(zf.namelist())},
223
- )
224
- )
225
-
226
- for num, f_path in enumerate(sorted(zf.namelist())):
227
- ext = Path(f_path).suffix
228
-
229
- if ext in (".gif", ".webp"):
230
- data = DecryptKakao.xor_data(zf.read(f_path))
231
- self.cb.put(f"Decrypted {f_path}")
232
- else:
233
- data = zf.read(f_path)
234
- self.cb.put(f"Read {f_path}")
235
-
236
- out_path = Path(self.out_dir, str(num).zfill(3) + ext)
237
- with open(out_path, "wb") as f:
238
- f.write(data)
239
-
240
- self.cb.put("update_bar")
241
-
242
- self.cb.put(f"Finished getting {pack_url}")
243
-
244
- return True
245
-
246
- @staticmethod
247
- def start(
248
- url: str,
249
- out_dir: Path,
250
- opt_cred: Optional[CredOption],
251
- cb: CallbackProtocol,
252
- cb_return: CallbackReturn,
253
- ) -> bool:
254
- downloader = DownloadKakao(url, out_dir, opt_cred, cb, cb_return)
255
- return downloader.download_stickers_kakao()