sticker-convert 2.14.0.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.
Files changed (37) hide show
  1. sticker_convert/auth/__init__.py +0 -0
  2. sticker_convert/auth/auth_base.py +19 -0
  3. sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +40 -13
  4. sticker_convert/{utils/auth/get_kakao_auth_android_login.py → auth/auth_kakao_android_login.py} +80 -84
  5. sticker_convert/{utils/auth/get_kakao_auth_desktop_login.py → auth/auth_kakao_desktop_login.py} +69 -28
  6. sticker_convert/{utils/auth/get_kakao_auth_desktop_memdump.py → auth/auth_kakao_desktop_memdump.py} +31 -24
  7. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +21 -6
  8. sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +18 -20
  9. sticker_convert/auth/auth_telethon.py +151 -0
  10. sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +19 -11
  11. sticker_convert/{utils/auth → auth}/telegram_api.py +4 -13
  12. sticker_convert/cli.py +44 -70
  13. sticker_convert/downloaders/download_line.py +2 -2
  14. sticker_convert/downloaders/download_telegram.py +1 -1
  15. sticker_convert/gui.py +15 -100
  16. sticker_convert/gui_components/frames/comp_frame.py +12 -4
  17. sticker_convert/gui_components/frames/config_frame.py +14 -6
  18. sticker_convert/gui_components/frames/control_frame.py +1 -1
  19. sticker_convert/gui_components/frames/cred_frame.py +6 -8
  20. sticker_convert/gui_components/windows/advanced_compression_window.py +3 -4
  21. sticker_convert/gui_components/windows/base_window.py +7 -2
  22. sticker_convert/gui_components/windows/discord_get_auth_window.py +3 -7
  23. sticker_convert/gui_components/windows/kakao_get_auth_window.py +79 -51
  24. sticker_convert/gui_components/windows/line_get_auth_window.py +5 -14
  25. sticker_convert/gui_components/windows/signal_get_auth_window.py +4 -12
  26. sticker_convert/gui_components/windows/viber_get_auth_window.py +8 -11
  27. sticker_convert/job.py +16 -32
  28. sticker_convert/uploaders/upload_telegram.py +1 -1
  29. sticker_convert/utils/callback.py +238 -6
  30. sticker_convert/version.py +1 -1
  31. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/METADATA +5 -5
  32. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/RECORD +36 -34
  33. sticker_convert/utils/auth/telethon_setup.py +0 -97
  34. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/WHEEL +0 -0
  35. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/entry_points.txt +0 -0
  36. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/licenses/LICENSE +0 -0
  37. {sticker_convert-2.14.0.0.dist-info → sticker_convert-2.15.0.0.dist-info}/top_level.txt +0 -0
@@ -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 Callable, Optional, Tuple, Union, cast
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,13 +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 GetKakaoAuthDesktopMemdump:
30
- def __init__(self, cb_ask_str: Callable[..., str] = input) -> None:
31
- self.cb_ask_str = cb_ask_str
32
- if platform.system() == "Windows":
33
- self.auth_token_length = 64
34
- else:
35
- self.auth_token_length = 52
28
+ class AuthKakaoDesktopMemdump(AuthBase):
29
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
30
+ super().__init__(*args, **kwargs)
36
31
 
37
32
  def launch_kakao(self, kakao_bin_path: str) -> None:
38
33
  if platform.system() == "Windows":
@@ -73,11 +68,12 @@ class GetKakaoAuthDesktopMemdump:
73
68
  ):
74
69
  auth_token_addr = cast(int, address) + 15
75
70
  auth_token_bytes = process.read_process_memory(
76
- auth_token_addr, bytes, self.auth_token_length + 1
71
+ auth_token_addr, bytes, 200
77
72
  )
78
- if auth_token_bytes[-1:] != b"\x00":
73
+ auth_token_term = auth_token_bytes.find(b"\x00")
74
+ if auth_token_term == -1:
79
75
  continue
80
- auth_token_candidate = auth_token_bytes[:-1].decode(
76
+ auth_token_candidate = auth_token_bytes[:auth_token_term].decode(
81
77
  encoding="ascii"
82
78
  )
83
79
  if len(auth_token_candidate) > 150:
@@ -114,12 +110,18 @@ class GetKakaoAuthDesktopMemdump:
114
110
  if relaunch and self.relaunch_kakao(kakao_bin_path) is None:
115
111
  return None, MSG_LAUNCH_FAIL
116
112
 
117
- if self.cb_ask_str == input:
118
- pw_func = getpass
119
- else:
120
- pw_func = partial(
121
- self.cb_ask_str, initialvalue="", cli_show_initialvalue=False
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
+ )
122
123
  )
124
+
123
125
  s = get_mem(kakao_pid, pw_func, is_wine)
124
126
 
125
127
  if s is None:
@@ -129,12 +131,13 @@ class GetKakaoAuthDesktopMemdump:
129
131
  for i in re.finditer(b"authorization: ", s):
130
132
  auth_token_addr = i.start() + 15
131
133
 
132
- auth_token_bytes = s[
133
- auth_token_addr : auth_token_addr + self.auth_token_length + 1
134
- ]
135
- if auth_token_bytes[-1:] != b"\x00":
136
- return None, MSG_NO_AUTH
137
- auth_token_candidate = auth_token_bytes[:-1].decode(encoding="ascii")
134
+ auth_token_bytes = s[auth_token_addr : auth_token_addr + 200]
135
+ auth_token_term = auth_token_bytes.find(b"\x00")
136
+ if auth_token_term == -1:
137
+ continue
138
+ auth_token_candidate = auth_token_bytes[:auth_token_term].decode(
139
+ encoding="ascii"
140
+ )
138
141
  if len(auth_token_candidate) > 150:
139
142
  auth_token = auth_token_candidate
140
143
  break
@@ -247,6 +250,8 @@ class GetKakaoAuthDesktopMemdump:
247
250
  # - Slow
248
251
  # - Cannot run on macOS
249
252
 
253
+ msg = "Getting Kakao authorization token by Desktop memdump...\n(This may take a minute)"
254
+ self.cb.put(("msg_dynamic", (msg,), None))
250
255
  if not kakao_bin_path:
251
256
  kakao_bin_path = self.get_kakao_desktop()
252
257
 
@@ -262,4 +267,6 @@ class GetKakaoAuthDesktopMemdump:
262
267
  else:
263
268
  kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
264
269
 
270
+ self.cb.put(("msg_dynamic", (None,), None))
271
+
265
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 GetLineAuth.validate_cookies(cookies_jar):
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 None
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 Callable, Optional, Tuple, cast
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 GetSignalAuth:
16
- def __init__(
17
- self,
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.cb_msg(download_url)
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
- if self.cb_ask_str != input:
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
- return uuid, password
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)
@@ -5,11 +5,10 @@ import platform
5
5
  import shutil
6
6
  import subprocess
7
7
  import time
8
- from functools import partial
9
- from getpass import getpass
10
8
  from pathlib import Path
11
- from typing import Callable, List, Optional, Tuple, cast
9
+ from typing import Any, Callable, List, Optional, Tuple, cast
12
10
 
11
+ from sticker_convert.auth.auth_base import AuthBase
13
12
  from sticker_convert.utils.process import check_admin, find_pid_by_name, get_mem, killall
14
13
 
15
14
  MSG_NO_BIN = """Viber Desktop not detected.
@@ -27,9 +26,9 @@ MSG_LAUNCH_FAIL = "Failed to launch Viber"
27
26
  MSG_PERMISSION_ERROR = "Failed to read Viber process memory"
28
27
 
29
28
 
30
- class GetViberAuth:
31
- def __init__(self, cb_ask_str: Callable[..., str] = input) -> None:
32
- self.cb_ask_str = cb_ask_str
29
+ class AuthViber(AuthBase):
30
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
31
+ super().__init__(*args, **kwargs)
33
32
 
34
33
  def relaunch_viber(self, viber_bin_path: str) -> Optional[int]:
35
34
  killed = killall("viber")
@@ -124,12 +123,18 @@ class GetViberAuth:
124
123
  if viber_pid is None:
125
124
  return None, MSG_LAUNCH_FAIL
126
125
 
127
- if self.cb_ask_str == input:
128
- pw_func = getpass
129
- else:
130
- pw_func = partial(
131
- self.cb_ask_str, initialvalue="", cli_show_initialvalue=False
126
+ def pw_func(msg: str) -> str:
127
+ return self.cb.put(
128
+ (
129
+ "ask_str",
130
+ None,
131
+ {
132
+ "message": msg,
133
+ "password": True,
134
+ },
135
+ )
132
136
  )
137
+
133
138
  s = get_mem(viber_pid, pw_func)
134
139
 
135
140
  if s is None:
@@ -189,6 +194,8 @@ class GetViberAuth:
189
194
  self,
190
195
  viber_bin_path: Optional[str] = None,
191
196
  ) -> Tuple[Optional[str], str]:
197
+ msg = "Getting Viber credentials...\n(This may take a minute)"
198
+ self.cb.put(("msg_dynamic", (msg,), None))
192
199
  if not viber_bin_path:
193
200
  viber_bin_path = self.get_viber_desktop()
194
201
 
@@ -232,4 +239,5 @@ class GetViberAuth:
232
239
  break
233
240
 
234
241
  killall("viber")
242
+ self.cb.put(("msg_dynamic", (None,), None))
235
243
  return viber_auth, msg
@@ -3,7 +3,7 @@ import re
3
3
  import time
4
4
  from collections import defaultdict
5
5
  from pathlib import Path
6
- from typing import Any, Dict, List, Optional, Protocol, Tuple, Union, cast
6
+ from typing import Any, Dict, List, Protocol, Tuple, Union, cast
7
7
 
8
8
  import anyio
9
9
  from telegram import InputSticker, PhotoSize, Sticker
@@ -15,8 +15,8 @@ from telethon.functions import messages # type: ignore
15
15
  from telethon.tl.types.messages import StickerSet as TLStickerSet # type: ignore
16
16
  from telethon.types import DocumentAttributeFilename, InputStickerSetShortName, InputStickerSetThumb, Message, TypeDocument # type: ignore
17
17
 
18
+ from sticker_convert.auth.auth_telethon import AuthTelethon
18
19
  from sticker_convert.job_option import CredOption
19
- from sticker_convert.utils.auth.telethon_setup import TelethonSetup
20
20
  from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
21
21
 
22
22
  # sticker_path: Path, sticker_bytes: bytes, emoji_list: List[str], sticker_format: str
@@ -343,8 +343,8 @@ class TelethonAPI(TelegramAPI):
343
343
  self.cb = cb
344
344
  self.cb_return = cb_return
345
345
 
346
- success, client, _, _ = await TelethonSetup(
347
- self.opt_cred, self.cb_ask_str
346
+ success, client, _, _, _ = await AuthTelethon(
347
+ self.opt_cred, self.cb
348
348
  ).start_async()
349
349
 
350
350
  if success is True and client is not None:
@@ -354,15 +354,6 @@ class TelethonAPI(TelegramAPI):
354
354
  async def exit(self) -> None:
355
355
  self.client.disconnect()
356
356
 
357
- def cb_ask_str(
358
- self, msg: Optional[str] = None, initialvalue: Optional[str] = None
359
- ) -> str:
360
- self.cb.put(("ask_str", (msg,), None))
361
- response = self.cb_return.get_response()
362
-
363
- assert isinstance(response, str)
364
- return response
365
-
366
357
  async def set_upload_pack_short_name(self, pack_title: str) -> str:
367
358
  self.pack_title = pack_title
368
359
  self.pack_short_name = re.sub(