sticker-convert 2.8.12__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 (124) hide show
  1. sticker_convert/__main__.py +24 -24
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/auth/auth_discord.py +149 -0
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/auth/auth_signal.py +139 -0
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/auth/auth_viber.py +250 -0
  12. sticker_convert/auth/telegram_api.py +736 -0
  13. sticker_convert/cli.py +623 -509
  14. sticker_convert/converter.py +1093 -962
  15. sticker_convert/definitions.py +11 -0
  16. sticker_convert/downloaders/download_band.py +111 -0
  17. sticker_convert/downloaders/download_base.py +171 -130
  18. sticker_convert/downloaders/download_discord.py +92 -0
  19. sticker_convert/downloaders/download_kakao.py +417 -255
  20. sticker_convert/downloaders/download_line.py +484 -472
  21. sticker_convert/downloaders/download_ogq.py +80 -0
  22. sticker_convert/downloaders/download_signal.py +108 -92
  23. sticker_convert/downloaders/download_telegram.py +56 -130
  24. sticker_convert/downloaders/download_viber.py +121 -95
  25. sticker_convert/gui.py +788 -795
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -165
  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 -162
  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 -715
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
  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 -135
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  42. sticker_convert/ios-message-stickers-template/README.md +10 -10
  43. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  50. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  51. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  52. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  53. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  54. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  55. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  56. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  57. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  58. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  59. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  60. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  61. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  62. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  63. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  64. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  65. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  66. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  67. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  68. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  69. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  70. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  71. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  72. sticker_convert/job.py +279 -179
  73. sticker_convert/job_option.py +15 -2
  74. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  75. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  76. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  77. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  78. sticker_convert/py.typed +0 -0
  79. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  80. sticker_convert/resources/compression.json +220 -16
  81. sticker_convert/resources/emoji.json +527 -77
  82. sticker_convert/resources/help.ja_JP.json +88 -0
  83. sticker_convert/resources/help.json +24 -10
  84. sticker_convert/resources/help.zh_CN.json +88 -0
  85. sticker_convert/resources/help.zh_TW.json +88 -0
  86. sticker_convert/resources/input.ja_JP.json +74 -0
  87. sticker_convert/resources/input.json +121 -71
  88. sticker_convert/resources/input.zh_CN.json +74 -0
  89. sticker_convert/resources/input.zh_TW.json +74 -0
  90. sticker_convert/resources/memdump_linux.sh +25 -0
  91. sticker_convert/resources/memdump_windows.ps1 +8 -0
  92. sticker_convert/resources/output.ja_JP.json +38 -0
  93. sticker_convert/resources/output.json +24 -0
  94. sticker_convert/resources/output.zh_CN.json +38 -0
  95. sticker_convert/resources/output.zh_TW.json +38 -0
  96. sticker_convert/uploaders/compress_wastickers.py +186 -156
  97. sticker_convert/uploaders/upload_base.py +44 -35
  98. sticker_convert/uploaders/upload_signal.py +218 -173
  99. sticker_convert/uploaders/upload_telegram.py +353 -388
  100. sticker_convert/uploaders/upload_viber.py +178 -0
  101. sticker_convert/uploaders/xcode_imessage.py +295 -285
  102. sticker_convert/utils/callback.py +238 -6
  103. sticker_convert/utils/chrome_remotedebug.py +219 -0
  104. sticker_convert/utils/chromiums/linux.py +52 -0
  105. sticker_convert/utils/chromiums/osx.py +68 -0
  106. sticker_convert/utils/chromiums/windows.py +45 -0
  107. sticker_convert/utils/emoji.py +28 -0
  108. sticker_convert/utils/files/json_resources_loader.py +24 -19
  109. sticker_convert/utils/files/metadata_handler.py +8 -7
  110. sticker_convert/utils/files/run_bin.py +1 -1
  111. sticker_convert/utils/media/codec_info.py +99 -67
  112. sticker_convert/utils/media/format_verify.py +33 -20
  113. sticker_convert/utils/process.py +231 -0
  114. sticker_convert/utils/translate.py +108 -0
  115. sticker_convert/utils/url_detect.py +40 -33
  116. sticker_convert/version.py +1 -1
  117. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
  118. sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
  119. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
  120. sticker_convert/utils/auth/get_signal_auth.py +0 -129
  121. sticker_convert-2.8.12.dist-info/RECORD +0 -101
  122. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  123. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
  124. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env python3
2
+ import base64
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import platform
7
+ import re
8
+ import shutil
9
+ import socket
10
+ import subprocess
11
+ import time
12
+ import uuid
13
+ from typing import Any, Optional, Tuple
14
+
15
+ import requests
16
+ from bs4 import BeautifulSoup
17
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
18
+
19
+ from sticker_convert.auth.auth_base import AuthBase
20
+ from sticker_convert.utils.translate import I
21
+
22
+
23
+ class AuthKakaoDesktopLogin(AuthBase):
24
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
25
+ self.OK_MSG = I("Login successful, auth_token={}")
26
+
27
+ super().__init__(*args, **kwargs)
28
+ self.username = self.opt_cred.kakao_username
29
+ self.password = self.opt_cred.kakao_password
30
+
31
+ if platform.system() == "Darwin":
32
+ self.plat = "mac"
33
+ user_agent_os = "Mc"
34
+ self.os_version = platform.mac_ver()[0]
35
+ version = self.macos_get_ver()
36
+ if version is None:
37
+ version = "25.2.0"
38
+ else:
39
+ self.plat = "win32"
40
+ user_agent_os = "Wd"
41
+ if platform.system() == "Windows":
42
+ self.os_version = platform.uname().version
43
+ else:
44
+ self.os_version = "10.0"
45
+ version = self.windows_get_ver()
46
+ if version is None:
47
+ version = "25.8.2"
48
+
49
+ user_agent = f"KT/{version} {user_agent_os}/{self.os_version} en"
50
+ self.headers = {
51
+ "user-agent": user_agent,
52
+ "a": f"{self.plat}/{version}/en",
53
+ "accept": "*/*",
54
+ "accept-encoding": "gzip, deflate, br",
55
+ "accept-language": "en",
56
+ "host": "katalk.kakao.com",
57
+ "Connection": "close",
58
+ }
59
+
60
+ self.device_name = socket.gethostname()
61
+ if platform.system() != "Darwin":
62
+ self.device_name = self.device_name.upper()
63
+ self.device_uuid = self.get_device_uuid()
64
+
65
+ hash = hashlib.sha512(
66
+ f"KLEAL|{self.username}|{self.device_uuid}|LCNUE|{user_agent}".encode(
67
+ "utf-8"
68
+ )
69
+ ).hexdigest()
70
+ xvc = hash[:16]
71
+
72
+ self.headers_login = self.headers.copy()
73
+ self.headers_login["x-vc"] = xvc
74
+
75
+ def pkcs7_pad(self, m: str) -> str:
76
+ return m + chr(16 - len(m) % 16) * (16 - len(m) % 16)
77
+
78
+ def windows_get_ver(self) -> Optional[str]:
79
+ r = requests.get(
80
+ "https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/k/Kakao/KakaoTalk"
81
+ )
82
+ rjson = json.loads(r.text)
83
+ if len(rjson) == 0:
84
+ return None
85
+ ver = rjson[-1]["name"]
86
+ return ".".join(ver.split(".")[:3])
87
+
88
+ def macos_get_ver(self) -> Optional[str]:
89
+ country = "us"
90
+ app_name = "kakaotalk-messenger"
91
+ app_id = "362057947"
92
+
93
+ url = f"https://apps.apple.com/{country}/app/{app_name}/id{app_id}"
94
+
95
+ r = requests.get(url)
96
+ soup = BeautifulSoup(r.text, "html.parser")
97
+ version_tag = soup.find("p", {"class": re.compile(".*latest__version")})
98
+ version_str = None
99
+ if version_tag is not None:
100
+ version_str = version_tag.text.replace("Version ", "")
101
+ return version_str
102
+
103
+ def windows_get_pragma(self, use_wine: bool = False) -> Optional[str]:
104
+ sys_uuid = None
105
+ hdd_model = None
106
+ hdd_serial = None
107
+
108
+ if use_wine and shutil.which("wine") is None:
109
+ return None
110
+
111
+ regkey = "HKEY_CURRENT_USER\\Software\\Kakao\\KakaoTalk\\DeviceInfo"
112
+ wine = "wine " if use_wine else ""
113
+ cmd = f"{wine}reg query '{regkey}' /v Last"
114
+ last_device_info = subprocess.run(
115
+ cmd, stdout=subprocess.PIPE, shell=True
116
+ ).stdout.decode()
117
+ if "REG_SZ" not in last_device_info:
118
+ return None
119
+ last_device_info = last_device_info.split("\n")[2].split()[2]
120
+
121
+ if self.opt_cred.kakao_device_uuid:
122
+ sys_uuid = self.opt_cred.kakao_device_uuid
123
+ else:
124
+ cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v sys_uuid"
125
+ sys_uuid = subprocess.run(
126
+ cmd, stdout=subprocess.PIPE, shell=True
127
+ ).stdout.decode()
128
+ if "REG_SZ" in sys_uuid:
129
+ sys_uuid = sys_uuid.split("\n")[2].split()[2]
130
+
131
+ cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v hdd_model"
132
+ hdd_model = subprocess.run(
133
+ cmd, stdout=subprocess.PIPE, shell=True
134
+ ).stdout.decode()
135
+ if "REG_SZ" in hdd_model:
136
+ hdd_model = hdd_model.split("\n")[2].split()[2]
137
+
138
+ cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v hdd_serial"
139
+ hdd_serial = subprocess.run(
140
+ cmd, stdout=subprocess.PIPE, shell=True
141
+ ).stdout.decode()
142
+ if "REG_SZ" in hdd_serial:
143
+ hdd_serial = hdd_serial.split("\n")[2].split()[2]
144
+
145
+ if sys_uuid and hdd_model and hdd_serial:
146
+ return f"{sys_uuid}|{hdd_model}|{hdd_serial}"
147
+ else:
148
+ return None
149
+
150
+ def get_device_uuid(self) -> str:
151
+ if platform.system() == "Darwin":
152
+ cmd = "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}'"
153
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True, check=True)
154
+ hwid = result.stdout.decode().strip().replace('"', "")
155
+
156
+ hwid_sha1 = bytes.fromhex(hashlib.sha1(hwid.encode()).hexdigest())
157
+ hwid_sha256 = bytes.fromhex(hashlib.sha256(hwid.encode()).hexdigest())
158
+
159
+ return base64.b64encode(hwid_sha1 + hwid_sha256).decode()
160
+ else:
161
+ use_wine = True if platform.system() != "Windows" else False
162
+ pragma = self.windows_get_pragma(use_wine=use_wine)
163
+ if pragma is None:
164
+ if platform.system() == "Windows":
165
+ if self.opt_cred.kakao_device_uuid:
166
+ sys_uuid = self.opt_cred.kakao_device_uuid
167
+ else:
168
+ cmd = "wmic csproduct get uuid"
169
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
170
+ sys_uuid = result.stdout.decode().split("\n")[1].strip()
171
+ cmd = "wmic diskdrive get Model"
172
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
173
+ hdd_model = result.stdout.decode().split("\n")[1].strip()
174
+ cmd = "wmic diskdrive get SerialNumber"
175
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
176
+ hdd_serial = result.stdout.decode().split("\n")[1].strip()
177
+ else:
178
+ if self.opt_cred.kakao_device_uuid:
179
+ sys_uuid = self.opt_cred.kakao_device_uuid
180
+ else:
181
+ product_uuid_path = "/sys/devices/virtual/dmi/id/product_uuid"
182
+ sys_uuid = None
183
+ if os.access(product_uuid_path, os.R_OK):
184
+ cmd = f"cat {product_uuid_path}"
185
+ result = subprocess.run(
186
+ cmd, stdout=subprocess.PIPE, shell=True
187
+ )
188
+ if result.returncode == 0:
189
+ sys_uuid = result.stdout.decode().strip()
190
+ if sys_uuid is None:
191
+ sys_uuid = str(uuid.uuid4()).upper()
192
+ self.opt_cred.kakao_device_uuid = sys_uuid
193
+ hdd_model = "Wine Disk Drive"
194
+ hdd_serial = ""
195
+ pragma = f"{sys_uuid}|{hdd_model}|{hdd_serial}"
196
+
197
+ aes_key = bytes.fromhex("9FBAE3118FDE5DEAEB8279D08F1D4C79")
198
+ aes_iv = bytes.fromhex("00000000000000000000000000000000")
199
+
200
+ cipher = Cipher(algorithms.AES(aes_key), modes.CBC(aes_iv))
201
+ encryptor = cipher.encryptor()
202
+ padded = self.pkcs7_pad(pragma).encode()
203
+ encrypted_pragma = encryptor.update(padded) + encryptor.finalize()
204
+ pragma_hash = hashlib.sha512(encrypted_pragma).digest()
205
+
206
+ return base64.b64encode(pragma_hash).decode()
207
+
208
+ def login(self, forced: bool = False):
209
+ data = {
210
+ "device_name": self.device_name,
211
+ "device_uuid": self.device_uuid,
212
+ "email": self.username,
213
+ "os_version": self.os_version,
214
+ "password": self.password,
215
+ "permanent": "1",
216
+ }
217
+
218
+ if forced:
219
+ data["forced"] = "1"
220
+
221
+ headers = self.headers_login.copy()
222
+ headers["content-type"] = "application/x-www-form-urlencoded"
223
+ response = requests.post(
224
+ f"https://katalk.kakao.com/{self.plat}/account/login.json",
225
+ headers=headers,
226
+ data=data,
227
+ )
228
+
229
+ return json.loads(response.text)
230
+
231
+ def generate_passcode(self):
232
+ data = {
233
+ "password": self.password,
234
+ "permanent": True,
235
+ "device": {
236
+ "name": self.device_name,
237
+ "osVersion": self.os_version,
238
+ "uuid": self.device_uuid,
239
+ },
240
+ "email": self.username,
241
+ }
242
+
243
+ if platform.system() == "Darwin":
244
+ cmd = "sysctl -n hw.model"
245
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True, check=True)
246
+ data["device"]["model"] = result.stdout.decode().strip() # type: ignore
247
+
248
+ headers = self.headers_login.copy()
249
+ headers["content-type"] = "application/json"
250
+ response = requests.post(
251
+ f"https://katalk.kakao.com/{self.plat}/account/passcodeLogin/generate",
252
+ headers=headers,
253
+ json=data,
254
+ )
255
+
256
+ return json.loads(response.text)
257
+
258
+ def register_device(self):
259
+ data = {
260
+ "device": {"uuid": self.device_uuid},
261
+ "email": self.username,
262
+ "password": self.password,
263
+ }
264
+
265
+ headers = self.headers_login.copy()
266
+ headers["content-type"] = "application/json"
267
+ response = None
268
+
269
+ response = requests.post(
270
+ f"https://katalk.kakao.com/{self.plat}/account/passcodeLogin/registerDevice",
271
+ headers=headers,
272
+ json=data,
273
+ )
274
+ return json.loads(response.text)
275
+
276
+ def get_cred(self) -> Tuple[Optional[str], str]:
277
+ msg = I("Getting Kakao authorization token by desktop login...")
278
+ self.cb.put(("msg_dynamic", (msg,), None))
279
+ rjson = self.login()
280
+ access_token = rjson.get("access_token")
281
+ if access_token is not None:
282
+ auth_token = access_token + "-" + self.device_uuid
283
+ self.cb.put(("msg_dynamic", (None,), None))
284
+ return auth_token, self.OK_MSG.format(auth_token)
285
+
286
+ rjson = self.generate_passcode()
287
+ if rjson.get("status") != 0:
288
+ return None, I("Failed to generate passcode: {}").format(rjson)
289
+ passcode = rjson["passcode"]
290
+
291
+ fail_reason = None
292
+ self.cb.put(("msg_dynamic", (None,), None))
293
+ while True:
294
+ rjson = self.register_device()
295
+ if rjson["status"] == 0:
296
+ break
297
+ elif rjson["status"] == -110:
298
+ fail_reason = "Timeout"
299
+ break
300
+ elif rjson["status"] != -100:
301
+ fail_reason = str(rjson)
302
+ break
303
+ time_remaining = rjson.get("remainingSeconds")
304
+ next_req_time = rjson.get("nextRequestIntervalInSeconds")
305
+ if time_remaining is None or next_req_time is None:
306
+ fail_reason = str(rjson)
307
+ msg = I(
308
+ "Please enter passcode in Kakao app on mobile device within {} seconds: {}"
309
+ ).format(time_remaining, passcode)
310
+ msg_dynamic_window_exist = self.cb.put(("msg_dynamic", (msg,), None))
311
+ if msg_dynamic_window_exist is False:
312
+ fail_reason = "Cancelled"
313
+ break
314
+ time.sleep(next_req_time)
315
+ self.cb.put(("msg_dynamic", (None,), None))
316
+ if fail_reason is not None:
317
+ return None, I("Failed to register device: {}").format(fail_reason)
318
+
319
+ rjson = self.login()
320
+ if rjson.get("status") == -101:
321
+ rjson = self.login(forced=True)
322
+ access_token = rjson.get("access_token")
323
+ if access_token is None:
324
+ return None, I("Failed to login after registering device: {}").format(rjson)
325
+
326
+ auth_token = access_token + "-" + self.device_uuid
327
+ return auth_token, self.OK_MSG.format(auth_token)
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import platform
4
+ import re
5
+ import subprocess
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Any, Optional, Tuple, Union, cast
9
+
10
+ from sticker_convert.auth.auth_base import AuthBase
11
+ from sticker_convert.utils.process import find_pid_by_name, get_mem, killall
12
+ from sticker_convert.utils.translate import I
13
+
14
+
15
+ class AuthKakaoDesktopMemdump(AuthBase):
16
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
17
+ self.MSG_NO_BIN = I(
18
+ "Kakao Desktop not detected.\n"
19
+ "Download and install Kakao Desktop,\n"
20
+ "then login to Kakao Desktop and try again."
21
+ )
22
+ self.MSG_NO_AUTH = I(
23
+ "Kakao Desktop installed,\n"
24
+ "but kakao_auth not found.\n"
25
+ "Please login to Kakao Desktop and try again."
26
+ )
27
+ self.MSG_SIP_ENABLED = I(
28
+ "You need to disable SIP:\n"
29
+ "1. Restart computer in Recovery mode\n"
30
+ "2. Launch Terminal from the Utilities menu\n"
31
+ "3. Run the command `csrutil disable`\n"
32
+ "4. Restart your computer"
33
+ )
34
+ self.MSG_LAUNCH_FAIL = I("Failed to launch Kakao")
35
+ self.MSG_PERMISSION_ERROR = I("Failed to read Kakao process memory")
36
+
37
+ super().__init__(*args, **kwargs)
38
+
39
+ def launch_kakao(self, kakao_bin_path: str) -> None:
40
+ if platform.system() == "Windows":
41
+ subprocess.Popen([kakao_bin_path])
42
+ elif platform.system() == "Darwin":
43
+ subprocess.Popen(["open", kakao_bin_path])
44
+ else:
45
+ subprocess.Popen(["wine", kakao_bin_path])
46
+
47
+ def relaunch_kakao(self, kakao_bin_path: str) -> Optional[int]:
48
+ killed = killall("kakaotalk")
49
+ if killed:
50
+ time.sleep(5)
51
+
52
+ self.launch_kakao(kakao_bin_path)
53
+ time.sleep(20)
54
+
55
+ return find_pid_by_name("kakaotalk")
56
+
57
+ def get_auth_by_pme(
58
+ self, kakao_bin_path: str, relaunch: bool = True
59
+ ) -> Tuple[Optional[str], str]:
60
+ from PyMemoryEditor import OpenProcess # type: ignore
61
+
62
+ auth_token = None
63
+
64
+ if relaunch:
65
+ kakao_pid = self.relaunch_kakao(kakao_bin_path)
66
+ else:
67
+ kakao_pid = find_pid_by_name("kakaotalk")
68
+ if kakao_pid is None:
69
+ return None, self.MSG_LAUNCH_FAIL
70
+
71
+ try:
72
+ with OpenProcess(pid=int(kakao_pid)) as process:
73
+ for address in process.search_by_value( # type: ignore
74
+ str, 15, "authorization: "
75
+ ):
76
+ auth_token_addr = cast(int, address) + 15
77
+ auth_token_bytes = process.read_process_memory(
78
+ auth_token_addr, bytes, 200
79
+ )
80
+ auth_token_term = auth_token_bytes.find(b"\x00")
81
+ if auth_token_term == -1:
82
+ continue
83
+ auth_token_candidate = auth_token_bytes[:auth_token_term].decode(
84
+ encoding="ascii"
85
+ )
86
+ if len(auth_token_candidate) > 150:
87
+ auth_token = auth_token_candidate
88
+ break
89
+ except PermissionError:
90
+ return None, self.MSG_PERMISSION_ERROR
91
+
92
+ if auth_token is None:
93
+ return None, self.MSG_NO_AUTH
94
+ else:
95
+ msg = "(Note: auth_token will be invalid if you quit Kakao Desktop)"
96
+ msg += "Got auth_token successfully:\n"
97
+ msg += f"{auth_token=}\n"
98
+
99
+ return auth_token, msg
100
+
101
+ def get_auth_by_dump(
102
+ self, kakao_bin_path: str, relaunch: bool = True
103
+ ) -> Tuple[Optional[str], str]:
104
+ auth_token = None
105
+ kakao_pid: Union[str, int, None]
106
+ if platform.system() == "Windows":
107
+ is_wine = False
108
+ if relaunch:
109
+ kakao_pid = self.relaunch_kakao(kakao_bin_path)
110
+ else:
111
+ kakao_pid = find_pid_by_name("kakaotalk")
112
+ if kakao_pid is None:
113
+ return None, self.MSG_LAUNCH_FAIL
114
+ else:
115
+ is_wine = True
116
+ kakao_pid = "KakaoTalk.exe"
117
+ if relaunch and self.relaunch_kakao(kakao_bin_path) is None:
118
+ return None, self.MSG_LAUNCH_FAIL
119
+
120
+ def pw_func(msg: str) -> str:
121
+ return self.cb.put(
122
+ (
123
+ "ask_str",
124
+ None,
125
+ {
126
+ "message": msg,
127
+ "password": True,
128
+ },
129
+ )
130
+ )
131
+
132
+ s = get_mem(kakao_pid, pw_func, is_wine)
133
+
134
+ if s is None:
135
+ return None, "Failed to dump memory"
136
+
137
+ auth_token = None
138
+ for i in re.finditer(b"authorization: ", s):
139
+ auth_token_addr = i.start() + 15
140
+
141
+ auth_token_bytes = s[auth_token_addr : auth_token_addr + 200]
142
+ auth_token_term = auth_token_bytes.find(b"\x00")
143
+ if auth_token_term == -1:
144
+ continue
145
+ auth_token_candidate = auth_token_bytes[:auth_token_term].decode(
146
+ encoding="ascii"
147
+ )
148
+ if len(auth_token_candidate) > 150:
149
+ auth_token = auth_token_candidate
150
+ break
151
+
152
+ if auth_token is None:
153
+ return None, self.MSG_NO_AUTH
154
+ else:
155
+ msg = "Got auth_token successfully:\n"
156
+ msg += f"{auth_token=}\n"
157
+
158
+ return auth_token, msg
159
+
160
+ def get_auth_darwin(self, kakao_bin_path: str) -> Tuple[Optional[str], str]:
161
+ csrutil_status = subprocess.run(
162
+ ["csrutil", "status"], capture_output=True, text=True
163
+ ).stdout
164
+
165
+ if "enabled" in csrutil_status:
166
+ return None, self.MSG_SIP_ENABLED
167
+
168
+ killall("kakaotalk")
169
+
170
+ subprocess.run(
171
+ [
172
+ "lldb",
173
+ kakao_bin_path,
174
+ "-o",
175
+ "b ptrace",
176
+ "-o",
177
+ "r",
178
+ "-o",
179
+ "thread return",
180
+ "-o",
181
+ "con",
182
+ "-o",
183
+ "process save-core /tmp/memdump.kakaotalk.dmp",
184
+ "-o",
185
+ "con",
186
+ "-o",
187
+ "quit",
188
+ ],
189
+ stdout=subprocess.DEVNULL,
190
+ stderr=subprocess.DEVNULL,
191
+ )
192
+
193
+ with open("/tmp/memdump.kakaotalk.dmp", "rb") as f:
194
+ mem = f.read()
195
+
196
+ os.remove("/tmp/memdump.kakaotalk.dmp")
197
+
198
+ auth_token = None
199
+ for i in re.finditer(b"]mac/", mem):
200
+ auth_token_term = i.start()
201
+
202
+ auth_token_bytes = mem[auth_token_term - 200 : auth_token_term]
203
+ auth_token_start = auth_token_bytes.find(b"application/json_\x10\x8a") + 19
204
+ if auth_token_start == -1:
205
+ continue
206
+ try:
207
+ auth_token_candidate = auth_token_bytes[auth_token_start:].decode(
208
+ encoding="ascii"
209
+ )
210
+ except UnicodeDecodeError:
211
+ continue
212
+
213
+ if 150 > len(auth_token_candidate) > 100:
214
+ auth_token = auth_token_candidate
215
+ break
216
+
217
+ if auth_token is None:
218
+ return None, self.MSG_NO_AUTH
219
+ else:
220
+ msg = "Got auth_token successfully:\n"
221
+ msg += f"{auth_token=}\n"
222
+
223
+ return auth_token, msg
224
+
225
+ def get_kakao_desktop(self) -> Optional[str]:
226
+ if platform.system() == "Windows":
227
+ kakao_bin_path = os.path.expandvars(
228
+ "%programfiles(x86)%\\Kakao\\KakaoTalk\\KakaoTalk.exe"
229
+ )
230
+ if Path(kakao_bin_path).exists() is False:
231
+ kakao_bin_path = os.path.expandvars(
232
+ "%programfiles%\\Kakao\\KakaoTalk\\KakaoTalk.exe"
233
+ )
234
+ elif platform.system() == "Darwin":
235
+ kakao_bin_path = "/Applications/KakaoTalk.app"
236
+ else:
237
+ wineprefix = os.environ.get("WINEPREFIX")
238
+ if not (wineprefix and Path(wineprefix).exists()):
239
+ wineprefix = os.path.expanduser("~/.wine")
240
+ kakao_bin_path = f"{wineprefix}/drive_c/Program Files (x86)/Kakao/KakaoTalk/KakaoTalk.exe"
241
+
242
+ if Path(kakao_bin_path).exists():
243
+ return kakao_bin_path
244
+
245
+ return None
246
+
247
+ def get_cred(
248
+ self,
249
+ kakao_bin_path: Optional[str] = None,
250
+ ) -> Tuple[Optional[str], str]:
251
+ # get_auth_by_dump()
252
+ # + Fast
253
+ # - Requires downloading procdump, or builtin method that needs admin
254
+
255
+ # get_auth_by_pme()
256
+ # + No admin (If have permission to read process)
257
+ # - Slow
258
+ # - Cannot run on macOS
259
+
260
+ msg = I(
261
+ "Getting Kakao authorization token by Desktop memdump...\n(This may take a minute)"
262
+ )
263
+ self.cb.put(("msg_dynamic", (msg,), None))
264
+ if not kakao_bin_path:
265
+ kakao_bin_path = self.get_kakao_desktop()
266
+
267
+ if not kakao_bin_path:
268
+ return None, self.MSG_NO_BIN
269
+
270
+ if platform.system() == "Windows":
271
+ kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
272
+ if kakao_auth is None:
273
+ kakao_auth, msg = self.get_auth_by_pme(kakao_bin_path, False)
274
+ elif platform.system() == "Darwin":
275
+ kakao_auth, msg = self.get_auth_darwin(kakao_bin_path)
276
+ else:
277
+ kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
278
+
279
+ self.cb.put(("msg_dynamic", (None,), None))
280
+
281
+ return kakao_auth, msg