sticker-convert 2.13.3.0__py3-none-any.whl → 2.15.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/auth/__init__.py +0 -0
- sticker_convert/auth/auth_base.py +19 -0
- sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +40 -13
- sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +80 -84
- sticker_convert/auth/auth_kakao_desktop_login.py +323 -0
- sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +21 -12
- sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +21 -6
- sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +18 -20
- sticker_convert/auth/auth_telethon.py +151 -0
- sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +19 -11
- sticker_convert/{utils/auth → auth}/telegram_api.py +10 -18
- sticker_convert/cli.py +57 -67
- sticker_convert/converter.py +4 -4
- sticker_convert/downloaders/download_line.py +2 -2
- sticker_convert/downloaders/download_telegram.py +1 -1
- sticker_convert/gui.py +20 -100
- sticker_convert/gui_components/frames/comp_frame.py +12 -4
- sticker_convert/gui_components/frames/config_frame.py +14 -6
- sticker_convert/gui_components/frames/control_frame.py +1 -1
- sticker_convert/gui_components/frames/cred_frame.py +6 -8
- sticker_convert/gui_components/windows/advanced_compression_window.py +3 -4
- sticker_convert/gui_components/windows/base_window.py +7 -2
- sticker_convert/gui_components/windows/discord_get_auth_window.py +3 -7
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +272 -97
- sticker_convert/gui_components/windows/line_get_auth_window.py +5 -14
- sticker_convert/gui_components/windows/signal_get_auth_window.py +4 -12
- sticker_convert/gui_components/windows/viber_get_auth_window.py +8 -11
- sticker_convert/job.py +16 -32
- sticker_convert/job_option.py +1 -0
- sticker_convert/resources/NotoColorEmoji.ttf +0 -0
- sticker_convert/resources/help.json +8 -6
- sticker_convert/uploaders/upload_telegram.py +1 -1
- sticker_convert/utils/callback.py +238 -6
- sticker_convert/version.py +1 -1
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/METADATA +41 -42
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/RECORD +40 -37
- sticker_convert/utils/auth/telethon_setup.py +0 -97
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/WHEEL +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/licenses/LICENSE +0 -0
- {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.15.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,323 @@
|
|
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
|
+
|
21
|
+
OK_MSG = "Login successful, auth_token: {auth_token}"
|
22
|
+
|
23
|
+
|
24
|
+
class AuthKakaoDesktopLogin(AuthBase):
|
25
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
26
|
+
super().__init__(*args, **kwargs)
|
27
|
+
self.username = self.opt_cred.kakao_username
|
28
|
+
self.password = self.opt_cred.kakao_password
|
29
|
+
|
30
|
+
if platform.system() == "Darwin":
|
31
|
+
self.plat = "mac"
|
32
|
+
user_agent_os = "Mc"
|
33
|
+
self.os_version = platform.mac_ver()[0]
|
34
|
+
version = self.macos_get_ver()
|
35
|
+
if version is None:
|
36
|
+
version = "25.2.0"
|
37
|
+
else:
|
38
|
+
self.plat = "win32"
|
39
|
+
user_agent_os = "Wd"
|
40
|
+
if platform.system() == "Windows":
|
41
|
+
self.os_version = platform.uname().version
|
42
|
+
else:
|
43
|
+
self.os_version = "10.0"
|
44
|
+
version = self.windows_get_ver()
|
45
|
+
if version is None:
|
46
|
+
version = "25.8.2"
|
47
|
+
|
48
|
+
user_agent = f"KT/{version} {user_agent_os}/{self.os_version} en"
|
49
|
+
self.headers = {
|
50
|
+
"user-agent": user_agent,
|
51
|
+
"a": f"{self.plat}/{version}/en",
|
52
|
+
"accept": "*/*",
|
53
|
+
"accept-encoding": "gzip, deflate, br",
|
54
|
+
"accept-language": "en",
|
55
|
+
"host": "katalk.kakao.com",
|
56
|
+
"Connection": "close",
|
57
|
+
}
|
58
|
+
|
59
|
+
self.device_name = socket.gethostname()
|
60
|
+
if platform.system() != "Darwin":
|
61
|
+
self.device_name = self.device_name.upper()
|
62
|
+
self.device_uuid = self.get_device_uuid()
|
63
|
+
|
64
|
+
hash = hashlib.sha512(
|
65
|
+
f"KLEAL|{self.username}|{self.device_uuid}|LCNUE|{user_agent}".encode(
|
66
|
+
"utf-8"
|
67
|
+
)
|
68
|
+
).hexdigest()
|
69
|
+
xvc = hash[:16]
|
70
|
+
|
71
|
+
self.headers_login = self.headers.copy()
|
72
|
+
self.headers_login["x-vc"] = xvc
|
73
|
+
|
74
|
+
def pkcs7_pad(self, m: str) -> str:
|
75
|
+
return m + chr(16 - len(m) % 16) * (16 - len(m) % 16)
|
76
|
+
|
77
|
+
def windows_get_ver(self) -> Optional[str]:
|
78
|
+
r = requests.get(
|
79
|
+
"https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/k/Kakao/KakaoTalk"
|
80
|
+
)
|
81
|
+
rjson = json.loads(r.text)
|
82
|
+
if len(rjson) == 0:
|
83
|
+
return None
|
84
|
+
ver = rjson[-1]["name"]
|
85
|
+
return ".".join(ver.split(".")[:3])
|
86
|
+
|
87
|
+
def macos_get_ver(self) -> Optional[str]:
|
88
|
+
country = "us"
|
89
|
+
app_name = "kakaotalk-messenger"
|
90
|
+
app_id = "362057947"
|
91
|
+
|
92
|
+
url = f"https://apps.apple.com/{country}/app/{app_name}/id{app_id}"
|
93
|
+
|
94
|
+
r = requests.get(url)
|
95
|
+
soup = BeautifulSoup(r.text, "html.parser")
|
96
|
+
version_tag = soup.find("p", {"class": re.compile(".*latest__version")})
|
97
|
+
version_str = None
|
98
|
+
if version_tag is not None:
|
99
|
+
version_str = version_tag.text.replace("Version ", "")
|
100
|
+
return version_str
|
101
|
+
|
102
|
+
def windows_get_pragma(self, use_wine: bool = False) -> Optional[str]:
|
103
|
+
sys_uuid = None
|
104
|
+
hdd_model = None
|
105
|
+
hdd_serial = None
|
106
|
+
|
107
|
+
if use_wine and shutil.which("wine") is None:
|
108
|
+
return None
|
109
|
+
|
110
|
+
regkey = "HKEY_CURRENT_USER\\Software\\Kakao\\KakaoTalk\\DeviceInfo"
|
111
|
+
wine = "wine " if use_wine else ""
|
112
|
+
cmd = f"{wine}reg query '{regkey}' /v Last"
|
113
|
+
last_device_info = subprocess.run(
|
114
|
+
cmd, stdout=subprocess.PIPE, shell=True
|
115
|
+
).stdout.decode()
|
116
|
+
if "REG_SZ" not in last_device_info:
|
117
|
+
return None
|
118
|
+
last_device_info = last_device_info.split("\n")[2].split()[2]
|
119
|
+
|
120
|
+
if self.opt_cred.kakao_device_uuid:
|
121
|
+
sys_uuid = self.opt_cred.kakao_device_uuid
|
122
|
+
else:
|
123
|
+
cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v sys_uuid"
|
124
|
+
sys_uuid = subprocess.run(
|
125
|
+
cmd, stdout=subprocess.PIPE, shell=True
|
126
|
+
).stdout.decode()
|
127
|
+
if "REG_SZ" in sys_uuid:
|
128
|
+
sys_uuid = sys_uuid.split("\n")[2].split()[2]
|
129
|
+
|
130
|
+
cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v hdd_model"
|
131
|
+
hdd_model = subprocess.run(
|
132
|
+
cmd, stdout=subprocess.PIPE, shell=True
|
133
|
+
).stdout.decode()
|
134
|
+
if "REG_SZ" in hdd_model:
|
135
|
+
hdd_model = hdd_model.split("\n")[2].split()[2]
|
136
|
+
|
137
|
+
cmd = f"{wine}reg query '{regkey}\\{last_device_info}' /v hdd_serial"
|
138
|
+
hdd_serial = subprocess.run(
|
139
|
+
cmd, stdout=subprocess.PIPE, shell=True
|
140
|
+
).stdout.decode()
|
141
|
+
if "REG_SZ" in hdd_serial:
|
142
|
+
hdd_serial = hdd_serial.split("\n")[2].split()[2]
|
143
|
+
|
144
|
+
if sys_uuid and hdd_model and hdd_serial:
|
145
|
+
return f"{sys_uuid}|{hdd_model}|{hdd_serial}"
|
146
|
+
else:
|
147
|
+
return None
|
148
|
+
|
149
|
+
def get_device_uuid(self) -> str:
|
150
|
+
if platform.system() == "Darwin":
|
151
|
+
cmd = "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID | awk '{print $3}'"
|
152
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True, check=True)
|
153
|
+
hwid = result.stdout.decode().strip().replace('"', "")
|
154
|
+
|
155
|
+
hwid_sha1 = bytes.fromhex(hashlib.sha1(hwid.encode()).hexdigest())
|
156
|
+
hwid_sha256 = bytes.fromhex(hashlib.sha256(hwid.encode()).hexdigest())
|
157
|
+
|
158
|
+
return base64.b64encode(hwid_sha1 + hwid_sha256).decode()
|
159
|
+
else:
|
160
|
+
use_wine = True if platform.system != "Windows" else False
|
161
|
+
pragma = self.windows_get_pragma(use_wine=use_wine)
|
162
|
+
if pragma is None:
|
163
|
+
if platform.system == "Windows":
|
164
|
+
if self.opt_cred.kakao_device_uuid:
|
165
|
+
sys_uuid = self.opt_cred.kakao_device_uuid
|
166
|
+
else:
|
167
|
+
cmd = "wmic csproduct get uuid"
|
168
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
169
|
+
sys_uuid = result.stdout.decode().split("\n")[1].strip()
|
170
|
+
cmd = "wmic diskdrive get Model"
|
171
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
172
|
+
hdd_model = result.stdout.decode().split("\n")[1].strip()
|
173
|
+
cmd = "wmic diskdrive get SerialNumber"
|
174
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
175
|
+
hdd_serial = result.stdout.decode().split("\n")[1].strip()
|
176
|
+
else:
|
177
|
+
if self.opt_cred.kakao_device_uuid:
|
178
|
+
sys_uuid = self.opt_cred.kakao_device_uuid
|
179
|
+
else:
|
180
|
+
product_uuid_path = "/sys/devices/virtual/dmi/id/product_uuid"
|
181
|
+
sys_uuid = None
|
182
|
+
if os.access(product_uuid_path, os.R_OK):
|
183
|
+
cmd = f"cat {product_uuid_path}"
|
184
|
+
result = subprocess.run(
|
185
|
+
cmd, stdout=subprocess.PIPE, shell=True
|
186
|
+
)
|
187
|
+
if result.returncode == 0:
|
188
|
+
sys_uuid = result.stdout.decode().strip()
|
189
|
+
if sys_uuid is None:
|
190
|
+
sys_uuid = str(uuid.uuid4()).upper()
|
191
|
+
self.opt_cred.kakao_device_uuid = sys_uuid
|
192
|
+
hdd_model = "Wine Disk Drive"
|
193
|
+
hdd_serial = ""
|
194
|
+
pragma = f"{sys_uuid}|{hdd_model}|{hdd_serial}"
|
195
|
+
|
196
|
+
aes_key = bytes.fromhex("9FBAE3118FDE5DEAEB8279D08F1D4C79")
|
197
|
+
aes_iv = bytes.fromhex("00000000000000000000000000000000")
|
198
|
+
|
199
|
+
cipher = Cipher(algorithms.AES(aes_key), modes.CBC(aes_iv))
|
200
|
+
encryptor = cipher.encryptor()
|
201
|
+
padded = self.pkcs7_pad(pragma).encode()
|
202
|
+
encrypted_pragma = encryptor.update(padded) + encryptor.finalize()
|
203
|
+
pragma_hash = hashlib.sha512(encrypted_pragma).digest()
|
204
|
+
|
205
|
+
return base64.b64encode(pragma_hash).decode()
|
206
|
+
|
207
|
+
def login(self, forced: bool = False):
|
208
|
+
data = {
|
209
|
+
"device_name": self.device_name,
|
210
|
+
"device_uuid": self.device_uuid,
|
211
|
+
"email": self.username,
|
212
|
+
"os_version": self.os_version,
|
213
|
+
"password": self.password,
|
214
|
+
"permanent": "1",
|
215
|
+
}
|
216
|
+
|
217
|
+
if forced:
|
218
|
+
data["forced"] = "1"
|
219
|
+
|
220
|
+
headers = self.headers_login.copy()
|
221
|
+
headers["content-type"] = "application/x-www-form-urlencoded"
|
222
|
+
response = requests.post(
|
223
|
+
f"https://katalk.kakao.com/{self.plat}/account/login.json",
|
224
|
+
headers=headers,
|
225
|
+
data=data,
|
226
|
+
)
|
227
|
+
|
228
|
+
return json.loads(response.text)
|
229
|
+
|
230
|
+
def generate_passcode(self):
|
231
|
+
data = {
|
232
|
+
"password": self.password,
|
233
|
+
"permanent": True,
|
234
|
+
"device": {
|
235
|
+
"name": self.device_name,
|
236
|
+
"osVersion": self.os_version,
|
237
|
+
"uuid": self.device_uuid,
|
238
|
+
},
|
239
|
+
"email": self.username,
|
240
|
+
}
|
241
|
+
|
242
|
+
if platform.system() == "Darwin":
|
243
|
+
cmd = "sysctl -n hw.model"
|
244
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True, check=True)
|
245
|
+
data["device"]["model"] = result.stdout.decode().strip() # type: ignore
|
246
|
+
|
247
|
+
headers = self.headers_login.copy()
|
248
|
+
headers["content-type"] = "application/json"
|
249
|
+
response = requests.post(
|
250
|
+
f"https://katalk.kakao.com/{self.plat}/account/passcodeLogin/generate",
|
251
|
+
headers=headers,
|
252
|
+
json=data,
|
253
|
+
)
|
254
|
+
|
255
|
+
return json.loads(response.text)
|
256
|
+
|
257
|
+
def register_device(self):
|
258
|
+
data = {
|
259
|
+
"device": {"uuid": self.device_uuid},
|
260
|
+
"email": self.username,
|
261
|
+
"password": self.password,
|
262
|
+
}
|
263
|
+
|
264
|
+
headers = self.headers_login.copy()
|
265
|
+
headers["content-type"] = "application/json"
|
266
|
+
response = None
|
267
|
+
|
268
|
+
response = requests.post(
|
269
|
+
f"https://katalk.kakao.com/{self.plat}/account/passcodeLogin/registerDevice",
|
270
|
+
headers=headers,
|
271
|
+
json=data,
|
272
|
+
)
|
273
|
+
return json.loads(response.text)
|
274
|
+
|
275
|
+
def get_cred(self) -> Tuple[Optional[str], str]:
|
276
|
+
msg = "Getting Kakao authorization token by desktop login..."
|
277
|
+
self.cb.put(("msg_dynamic", (msg,), None))
|
278
|
+
rjson = self.login()
|
279
|
+
access_token = rjson.get("access_token")
|
280
|
+
if access_token is not None:
|
281
|
+
auth_token = access_token + "-" + self.device_uuid
|
282
|
+
return auth_token, OK_MSG.format(auth_token=auth_token)
|
283
|
+
|
284
|
+
rjson = self.generate_passcode()
|
285
|
+
if rjson.get("status") != 0:
|
286
|
+
return None, f"Failed to generate passcode: {rjson}"
|
287
|
+
passcode = rjson["passcode"]
|
288
|
+
|
289
|
+
fail_reason = None
|
290
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
291
|
+
while True:
|
292
|
+
rjson = self.register_device()
|
293
|
+
if rjson["status"] == 0:
|
294
|
+
break
|
295
|
+
elif rjson["status"] == -110:
|
296
|
+
fail_reason = "Timeout"
|
297
|
+
break
|
298
|
+
elif rjson["status"] != -100:
|
299
|
+
fail_reason = str(rjson)
|
300
|
+
break
|
301
|
+
time_remaining = rjson.get("remainingSeconds")
|
302
|
+
next_req_time = rjson.get("nextRequestIntervalInSeconds")
|
303
|
+
if time_remaining is None or next_req_time is None:
|
304
|
+
fail_reason = str(rjson)
|
305
|
+
msg = f"Please enter passcode in Kakao app on mobile device within {time_remaining} seconds: {passcode}"
|
306
|
+
msg_dynamic_window_exist = self.cb.put(("msg_dynamic", (msg,), None))
|
307
|
+
if msg_dynamic_window_exist is False:
|
308
|
+
fail_reason = "Cancelled"
|
309
|
+
break
|
310
|
+
time.sleep(next_req_time)
|
311
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
312
|
+
if fail_reason is not None:
|
313
|
+
return None, f"Failed to register device: {fail_reason}"
|
314
|
+
|
315
|
+
rjson = self.login()
|
316
|
+
if rjson.get("status") == -101:
|
317
|
+
rjson = self.login(forced=True)
|
318
|
+
access_token = rjson.get("access_token")
|
319
|
+
if access_token is None:
|
320
|
+
return None, f"Failed to login after registering device: {rjson}"
|
321
|
+
|
322
|
+
auth_token = access_token + "-" + self.device_uuid
|
323
|
+
return auth_token, OK_MSG.format(auth_token=auth_token)
|
@@ -4,11 +4,10 @@ import platform
|
|
4
4
|
import re
|
5
5
|
import subprocess
|
6
6
|
import time
|
7
|
-
from functools import partial
|
8
|
-
from getpass import getpass
|
9
7
|
from pathlib import Path
|
10
|
-
from typing import
|
8
|
+
from typing import Any, Optional, Tuple, Union, cast
|
11
9
|
|
10
|
+
from sticker_convert.auth.auth_base import AuthBase
|
12
11
|
from sticker_convert.utils.process import find_pid_by_name, get_mem, killall
|
13
12
|
|
14
13
|
MSG_NO_BIN = """Kakao Desktop not detected.
|
@@ -26,9 +25,9 @@ MSG_LAUNCH_FAIL = "Failed to launch Kakao"
|
|
26
25
|
MSG_PERMISSION_ERROR = "Failed to read Kakao process memory"
|
27
26
|
|
28
27
|
|
29
|
-
class
|
30
|
-
def __init__(self,
|
31
|
-
|
28
|
+
class AuthKakaoDesktopMemdump(AuthBase):
|
29
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
30
|
+
super().__init__(*args, **kwargs)
|
32
31
|
|
33
32
|
def launch_kakao(self, kakao_bin_path: str) -> None:
|
34
33
|
if platform.system() == "Windows":
|
@@ -111,12 +110,18 @@ class GetKakaoDesktopAuth:
|
|
111
110
|
if relaunch and self.relaunch_kakao(kakao_bin_path) is None:
|
112
111
|
return None, MSG_LAUNCH_FAIL
|
113
112
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
113
|
+
def pw_func(msg: str) -> str:
|
114
|
+
return self.cb.put(
|
115
|
+
(
|
116
|
+
"ask_str",
|
117
|
+
None,
|
118
|
+
{
|
119
|
+
"message": msg,
|
120
|
+
"password": True,
|
121
|
+
},
|
122
|
+
)
|
119
123
|
)
|
124
|
+
|
120
125
|
s = get_mem(kakao_pid, pw_func, is_wine)
|
121
126
|
|
122
127
|
if s is None:
|
@@ -129,7 +134,7 @@ class GetKakaoDesktopAuth:
|
|
129
134
|
auth_token_bytes = s[auth_token_addr : auth_token_addr + 200]
|
130
135
|
auth_token_term = auth_token_bytes.find(b"\x00")
|
131
136
|
if auth_token_term == -1:
|
132
|
-
|
137
|
+
continue
|
133
138
|
auth_token_candidate = auth_token_bytes[:auth_token_term].decode(
|
134
139
|
encoding="ascii"
|
135
140
|
)
|
@@ -245,6 +250,8 @@ class GetKakaoDesktopAuth:
|
|
245
250
|
# - Slow
|
246
251
|
# - Cannot run on macOS
|
247
252
|
|
253
|
+
msg = "Getting Kakao authorization token by Desktop memdump...\n(This may take a minute)"
|
254
|
+
self.cb.put(("msg_dynamic", (msg,), None))
|
248
255
|
if not kakao_bin_path:
|
249
256
|
kakao_bin_path = self.get_kakao_desktop()
|
250
257
|
|
@@ -260,4 +267,6 @@ class GetKakaoDesktopAuth:
|
|
260
267
|
else:
|
261
268
|
kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
|
262
269
|
|
270
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
271
|
+
|
263
272
|
return kakao_auth, msg
|
@@ -2,14 +2,25 @@
|
|
2
2
|
import json
|
3
3
|
import platform
|
4
4
|
from http.cookiejar import CookieJar
|
5
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
6
6
|
|
7
7
|
import requests
|
8
8
|
import rookiepy
|
9
9
|
|
10
|
+
from sticker_convert.auth.auth_base import AuthBase
|
11
|
+
|
12
|
+
OK_MSG = "Got Line cookies successfully"
|
13
|
+
FAIL_MSG = "Failed to get Line cookies. Have you logged in the web browser?"
|
14
|
+
|
15
|
+
|
16
|
+
class AuthLine(AuthBase):
|
17
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
18
|
+
super().__init__(*args, **kwargs)
|
19
|
+
|
20
|
+
def get_cred(self) -> Tuple[Optional[str], str]:
|
21
|
+
msg = "Getting Line cookies"
|
22
|
+
self.cb.put(("msg_dynamic", (msg,), None))
|
10
23
|
|
11
|
-
class GetLineAuth:
|
12
|
-
def get_cred(self) -> Optional[str]:
|
13
24
|
browsers: List[Callable[..., Any]] = [
|
14
25
|
rookiepy.load, # Supposed to load from any browser, but may fail
|
15
26
|
rookiepy.firefox,
|
@@ -44,19 +55,23 @@ class GetLineAuth:
|
|
44
55
|
cookies_dict = browser(["store.line.me"])
|
45
56
|
cookies_jar = rookiepy.to_cookiejar(cookies_dict)
|
46
57
|
|
47
|
-
if
|
58
|
+
if AuthLine.validate_cookies(cookies_jar):
|
48
59
|
break
|
49
60
|
|
50
61
|
except Exception:
|
51
62
|
continue
|
52
63
|
|
64
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
53
65
|
if cookies_dict is None or cookies_jar is None:
|
54
|
-
return
|
66
|
+
return (
|
67
|
+
None,
|
68
|
+
"Failed to get Line cookies. Have you logged in the web browser?",
|
69
|
+
)
|
55
70
|
|
56
71
|
cookies_list = ["%s=%s" % (i["name"], i["value"]) for i in cookies_dict]
|
57
72
|
cookies = ";".join(cookies_list)
|
58
73
|
|
59
|
-
return cookies
|
74
|
+
return cookies, OK_MSG
|
60
75
|
|
61
76
|
@staticmethod
|
62
77
|
def validate_cookies(cookies: Union[CookieJar, Dict[str, str]]) -> bool:
|
@@ -5,43 +5,33 @@ import platform
|
|
5
5
|
import shutil
|
6
6
|
import time
|
7
7
|
import webbrowser
|
8
|
-
from typing import
|
8
|
+
from typing import Any, Optional, Tuple, cast
|
9
9
|
|
10
|
+
from sticker_convert.auth.auth_base import AuthBase
|
10
11
|
from sticker_convert.definitions import CONFIG_DIR
|
11
12
|
from sticker_convert.utils.chrome_remotedebug import CRD
|
12
13
|
from sticker_convert.utils.process import killall
|
13
14
|
|
14
15
|
|
15
|
-
class
|
16
|
-
def __init__(
|
17
|
-
|
18
|
-
cb_msg: Callable[..., None] = print,
|
19
|
-
cb_ask_str: Callable[..., str] = input,
|
20
|
-
) -> None:
|
16
|
+
class AuthSignal(AuthBase):
|
17
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
18
|
+
super().__init__(*args, **kwargs)
|
21
19
|
chromedriver_download_dir = CONFIG_DIR / "bin"
|
22
20
|
os.makedirs(chromedriver_download_dir, exist_ok=True)
|
23
21
|
|
24
22
|
self.chromedriver_download_dir = chromedriver_download_dir
|
25
23
|
|
26
|
-
self.cb_ask_str = cb_ask_str
|
27
|
-
self.cb_msg = cb_msg
|
28
|
-
|
29
24
|
def download_signal_desktop(self) -> None:
|
30
25
|
download_url = "https://signal.org/en/download/"
|
31
26
|
|
32
27
|
webbrowser.open(download_url)
|
33
28
|
|
34
|
-
self.
|
29
|
+
self.cb.put(download_url)
|
35
30
|
|
36
31
|
prompt = "Signal Desktop not detected.\n"
|
37
32
|
prompt += "Download and install Signal Desktop version\n"
|
38
33
|
prompt += "After installation, quit Signal Desktop before continuing"
|
39
|
-
|
40
|
-
self.cb_ask_str(
|
41
|
-
prompt, initialvalue=download_url, cli_show_initialvalue=False
|
42
|
-
)
|
43
|
-
else:
|
44
|
-
self.cb_msg(prompt)
|
34
|
+
self.cb.put(("ask_str", (prompt,), None))
|
45
35
|
|
46
36
|
def get_signal_bin_path(self) -> Optional[str]:
|
47
37
|
signal_paths: Tuple[Optional[str], ...]
|
@@ -73,11 +63,14 @@ class GetSignalAuth:
|
|
73
63
|
return signal_path
|
74
64
|
return None
|
75
65
|
|
76
|
-
def get_cred(self) -> Tuple[Optional[str], Optional[str]]:
|
66
|
+
def get_cred(self) -> Tuple[Optional[str], Optional[str], str]:
|
67
|
+
msg = "Getting Signal credentials..."
|
68
|
+
self.cb.put(("msg_dynamic", (msg,), None))
|
77
69
|
signal_bin_path = self.get_signal_bin_path()
|
78
70
|
if signal_bin_path is None:
|
71
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
79
72
|
self.download_signal_desktop()
|
80
|
-
return None, None
|
73
|
+
return None, None, "Failed to get uuid and password"
|
81
74
|
|
82
75
|
if platform.system() == "Windows":
|
83
76
|
killall("signal")
|
@@ -132,4 +125,9 @@ class GetSignalAuth:
|
|
132
125
|
time.sleep(1)
|
133
126
|
|
134
127
|
crd.close()
|
135
|
-
|
128
|
+
self.cb.put(("msg_dynamic", (None,), None))
|
129
|
+
return (
|
130
|
+
uuid,
|
131
|
+
password,
|
132
|
+
f"Got uuid and password successfully:\nuuid={uuid}\npassword={password}",
|
133
|
+
)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
from typing import Any, Optional, Tuple
|
3
|
+
|
4
|
+
import anyio
|
5
|
+
from telethon import TelegramClient # type: ignore
|
6
|
+
from telethon.errors import RPCError, SessionPasswordNeededError # type: ignore
|
7
|
+
|
8
|
+
from sticker_convert.auth.auth_base import AuthBase
|
9
|
+
from sticker_convert.definitions import CONFIG_DIR
|
10
|
+
|
11
|
+
GUIDE_MSG = """1. Visit https://my.telegram.org
|
12
|
+
2. Login using your phone number
|
13
|
+
3. Go to "API development tools"
|
14
|
+
4. Fill form
|
15
|
+
- App title: sticker-convert
|
16
|
+
- Short name: sticker-convert
|
17
|
+
- URL: www.telegram.org
|
18
|
+
- Platform: Desktop
|
19
|
+
- Description: sticker-convert
|
20
|
+
5. Note down api_id and api_hash
|
21
|
+
Continue when done"""
|
22
|
+
OK_MSG = "Telethon setup successful"
|
23
|
+
FAIL_MSG = "Telethon setup failed"
|
24
|
+
|
25
|
+
|
26
|
+
class AuthTelethon(AuthBase):
|
27
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
28
|
+
super().__init__(*args, **kwargs)
|
29
|
+
|
30
|
+
async def signin_async(
|
31
|
+
self, try_sign_in: bool = True
|
32
|
+
) -> Tuple[bool, TelegramClient, int, str, str]:
|
33
|
+
client = TelegramClient(
|
34
|
+
CONFIG_DIR / f"telethon-{self.opt_cred.telethon_api_id}.session",
|
35
|
+
self.opt_cred.telethon_api_id,
|
36
|
+
self.opt_cred.telethon_api_hash,
|
37
|
+
)
|
38
|
+
|
39
|
+
await client.connect()
|
40
|
+
authed = await client.is_user_authorized()
|
41
|
+
if authed is False and try_sign_in is True:
|
42
|
+
error_msg = ""
|
43
|
+
while True:
|
44
|
+
msg = f"{error_msg}Enter phone number: "
|
45
|
+
phone_number = self.cb.put(("ask_str", (msg,), None))
|
46
|
+
if phone_number == "":
|
47
|
+
return False, client, 0, "", FAIL_MSG
|
48
|
+
try:
|
49
|
+
await client.send_code_request(phone_number)
|
50
|
+
break
|
51
|
+
except RPCError as e:
|
52
|
+
error_msg = f"Error: {e}\n"
|
53
|
+
|
54
|
+
error_msg = ""
|
55
|
+
while True:
|
56
|
+
msg = f"{error_msg}Enter code: "
|
57
|
+
code = self.cb.put(("ask_str", (msg,), None))
|
58
|
+
if code == "":
|
59
|
+
return False, client, 0, "", FAIL_MSG
|
60
|
+
try:
|
61
|
+
await client.sign_in(phone_number, code)
|
62
|
+
break
|
63
|
+
except SessionPasswordNeededError:
|
64
|
+
password = self.cb.put(
|
65
|
+
(
|
66
|
+
"ask_str",
|
67
|
+
None,
|
68
|
+
{"question": "Enter password: ", "password": True},
|
69
|
+
)
|
70
|
+
)
|
71
|
+
try:
|
72
|
+
await client.sign_in(password=password)
|
73
|
+
break
|
74
|
+
except RPCError as e:
|
75
|
+
error_msg = f"Error: {e}\n"
|
76
|
+
except RPCError as e:
|
77
|
+
error_msg = f"Error: {e}\n"
|
78
|
+
authed = await client.is_user_authorized()
|
79
|
+
|
80
|
+
return (
|
81
|
+
authed,
|
82
|
+
client,
|
83
|
+
self.opt_cred.telethon_api_id,
|
84
|
+
self.opt_cred.telethon_api_hash,
|
85
|
+
OK_MSG if authed else FAIL_MSG,
|
86
|
+
)
|
87
|
+
|
88
|
+
def guide(self) -> None:
|
89
|
+
self.cb.put(("msg_block", (GUIDE_MSG,), None))
|
90
|
+
|
91
|
+
def get_api_info(self) -> bool:
|
92
|
+
api_id_ask = "Enter api_id: "
|
93
|
+
wrong_hint = ""
|
94
|
+
|
95
|
+
while True:
|
96
|
+
telethon_api_id = self.cb.put(
|
97
|
+
(
|
98
|
+
"ask_str",
|
99
|
+
None,
|
100
|
+
{
|
101
|
+
"question": wrong_hint + api_id_ask,
|
102
|
+
"initialvalue": str(self.opt_cred.telethon_api_id),
|
103
|
+
},
|
104
|
+
)
|
105
|
+
)
|
106
|
+
if telethon_api_id == "":
|
107
|
+
return False
|
108
|
+
elif telethon_api_id.isnumeric():
|
109
|
+
self.opt_cred.telethon_api_id = int(telethon_api_id)
|
110
|
+
break
|
111
|
+
else:
|
112
|
+
wrong_hint = "Error: api_id should be numeric\n"
|
113
|
+
|
114
|
+
msg = "Enter api_hash: "
|
115
|
+
self.opt_cred.telethon_api_hash = self.cb.put(
|
116
|
+
(
|
117
|
+
"ask_str",
|
118
|
+
None,
|
119
|
+
{"question": msg, "initialvalue": self.opt_cred.telethon_api_hash},
|
120
|
+
)
|
121
|
+
)
|
122
|
+
if self.opt_cred.telethon_api_hash == "":
|
123
|
+
return False
|
124
|
+
return True
|
125
|
+
|
126
|
+
async def start_async(
|
127
|
+
self, check_auth_only: bool = False
|
128
|
+
) -> Tuple[bool, Optional[TelegramClient], int, str, str]:
|
129
|
+
if self.opt_cred.telethon_api_id != 0 and self.opt_cred.telethon_api_hash != "":
|
130
|
+
success, client, api_id, api_hash, msg = await self.signin_async(
|
131
|
+
try_sign_in=False
|
132
|
+
)
|
133
|
+
if check_auth_only:
|
134
|
+
client.disconnect()
|
135
|
+
if success is True:
|
136
|
+
return success, client, api_id, api_hash, msg
|
137
|
+
|
138
|
+
self.guide()
|
139
|
+
cred_valid = self.get_api_info()
|
140
|
+
if cred_valid:
|
141
|
+
success, client, api_id, api_hash, msg = await self.signin_async()
|
142
|
+
if check_auth_only:
|
143
|
+
client.disconnect()
|
144
|
+
return success, client, api_id, api_hash, msg
|
145
|
+
else:
|
146
|
+
return False, None, 0, "", FAIL_MSG
|
147
|
+
|
148
|
+
def start(
|
149
|
+
self, check_auth_only: bool = False
|
150
|
+
) -> Tuple[bool, Optional[TelegramClient], int, str, str]:
|
151
|
+
return anyio.run(self.start_async, check_auth_only)
|