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.
- sticker_convert/__main__.py +24 -24
- sticker_convert/auth/__init__.py +0 -0
- sticker_convert/auth/auth_base.py +19 -0
- sticker_convert/auth/auth_discord.py +149 -0
- sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
- sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
- sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
- sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
- sticker_convert/auth/auth_signal.py +139 -0
- sticker_convert/auth/auth_telethon.py +161 -0
- sticker_convert/auth/auth_viber.py +250 -0
- sticker_convert/auth/telegram_api.py +736 -0
- sticker_convert/cli.py +623 -509
- sticker_convert/converter.py +1093 -962
- sticker_convert/definitions.py +11 -0
- sticker_convert/downloaders/download_band.py +111 -0
- sticker_convert/downloaders/download_base.py +171 -130
- sticker_convert/downloaders/download_discord.py +92 -0
- sticker_convert/downloaders/download_kakao.py +417 -255
- sticker_convert/downloaders/download_line.py +484 -472
- sticker_convert/downloaders/download_ogq.py +80 -0
- sticker_convert/downloaders/download_signal.py +108 -92
- sticker_convert/downloaders/download_telegram.py +56 -130
- sticker_convert/downloaders/download_viber.py +121 -95
- sticker_convert/gui.py +788 -795
- sticker_convert/gui_components/frames/comp_frame.py +180 -165
- sticker_convert/gui_components/frames/config_frame.py +156 -113
- sticker_convert/gui_components/frames/control_frame.py +32 -30
- sticker_convert/gui_components/frames/cred_frame.py +232 -162
- sticker_convert/gui_components/frames/input_frame.py +139 -137
- sticker_convert/gui_components/frames/output_frame.py +112 -110
- sticker_convert/gui_components/frames/right_clicker.py +25 -23
- sticker_convert/gui_components/windows/advanced_compression_window.py +757 -715
- sticker_convert/gui_components/windows/base_window.py +7 -2
- sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
- sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
- sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -135
- sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
- sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
- sticker_convert/ios-message-stickers-template/.gitignore +0 -0
- sticker_convert/ios-message-stickers-template/README.md +10 -10
- sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
- sticker_convert/job.py +279 -179
- sticker_convert/job_option.py +15 -2
- sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
- sticker_convert/py.typed +0 -0
- sticker_convert/resources/NotoColorEmoji.ttf +0 -0
- sticker_convert/resources/compression.json +220 -16
- sticker_convert/resources/emoji.json +527 -77
- sticker_convert/resources/help.ja_JP.json +88 -0
- sticker_convert/resources/help.json +24 -10
- sticker_convert/resources/help.zh_CN.json +88 -0
- sticker_convert/resources/help.zh_TW.json +88 -0
- sticker_convert/resources/input.ja_JP.json +74 -0
- sticker_convert/resources/input.json +121 -71
- sticker_convert/resources/input.zh_CN.json +74 -0
- sticker_convert/resources/input.zh_TW.json +74 -0
- sticker_convert/resources/memdump_linux.sh +25 -0
- sticker_convert/resources/memdump_windows.ps1 +8 -0
- sticker_convert/resources/output.ja_JP.json +38 -0
- sticker_convert/resources/output.json +24 -0
- sticker_convert/resources/output.zh_CN.json +38 -0
- sticker_convert/resources/output.zh_TW.json +38 -0
- sticker_convert/uploaders/compress_wastickers.py +186 -156
- sticker_convert/uploaders/upload_base.py +44 -35
- sticker_convert/uploaders/upload_signal.py +218 -173
- sticker_convert/uploaders/upload_telegram.py +353 -388
- sticker_convert/uploaders/upload_viber.py +178 -0
- sticker_convert/uploaders/xcode_imessage.py +295 -285
- sticker_convert/utils/callback.py +238 -6
- sticker_convert/utils/chrome_remotedebug.py +219 -0
- sticker_convert/utils/chromiums/linux.py +52 -0
- sticker_convert/utils/chromiums/osx.py +68 -0
- sticker_convert/utils/chromiums/windows.py +45 -0
- sticker_convert/utils/emoji.py +28 -0
- sticker_convert/utils/files/json_resources_loader.py +24 -19
- sticker_convert/utils/files/metadata_handler.py +8 -7
- sticker_convert/utils/files/run_bin.py +1 -1
- sticker_convert/utils/media/codec_info.py +99 -67
- sticker_convert/utils/media/format_verify.py +33 -20
- sticker_convert/utils/process.py +231 -0
- sticker_convert/utils/translate.py +108 -0
- sticker_convert/utils/url_detect.py +40 -33
- sticker_convert/version.py +1 -1
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
- sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
- sticker_convert/utils/auth/get_signal_auth.py +0 -129
- sticker_convert-2.8.12.dist-info/RECORD +0 -101
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
- {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
|