sticker-convert 2.9.0__py3-none-any.whl → 2.9.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -92,10 +92,10 @@ class DownloadBase:
92
92
  response = requests.get(
93
93
  url, stream=True, allow_redirects=True, **kwargs
94
94
  )
95
- total_length = int(response.headers.get("content-length")) # type: ignore
96
-
97
95
  if not response.ok:
98
96
  return b""
97
+ total_length = int(response.headers.get("content-length")) # type: ignore
98
+
99
99
  self.cb.put(f"Downloading {url}")
100
100
 
101
101
  if show_progress:
@@ -3,16 +3,20 @@ from __future__ import annotations
3
3
 
4
4
  import itertools
5
5
  import json
6
- import re
7
- import zipfile
8
- from io import BytesIO
6
+ import webbrowser
9
7
  from pathlib import Path
8
+ from tempfile import TemporaryDirectory
10
9
  from typing import Any, List, Optional, Tuple, cast
11
10
  from urllib.parse import urlparse
12
11
 
12
+ import chromedriver_autoinstaller # type: ignore
13
13
  import requests
14
14
  from bs4 import BeautifulSoup
15
- from bs4.element import Tag
15
+ from selenium import webdriver
16
+ from selenium.webdriver.chrome.options import Options
17
+ from selenium.webdriver.common.by import By
18
+ from selenium.webdriver.support import expected_conditions as EC
19
+ from selenium.webdriver.support.wait import WebDriverWait
16
20
 
17
21
  from sticker_convert.downloaders.download_base import DownloadBase
18
22
  from sticker_convert.job_option import CredOption
@@ -20,94 +24,38 @@ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
20
24
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
21
25
  from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
22
26
 
23
-
24
- def search_bracket(text: str, open_bracket: str = "{", close_bracket: str = "}") -> int:
25
- depth = 0
26
- is_str = False
27
-
28
- for count, char in enumerate(text):
29
- if char == '"':
30
- is_str = not is_str
31
-
32
- if is_str is False:
33
- if char == open_bracket:
34
- depth += 1
35
- elif char == close_bracket:
36
- depth -= 1
37
-
38
- if depth == 0:
39
- return count
40
-
41
- return -1
27
+ JSINJECT = """
28
+ class osclass {
29
+ android = true;
30
+ }
31
+ class uaclass {
32
+ os = new osclass();
33
+ }
34
+ class util {
35
+ static userAgent() {
36
+ return new uaclass();
37
+ }
38
+ }
39
+ class daumtools {
40
+ static web2app(dataDict) {
41
+ return dataDict['urlScheme'];
42
+ }
43
+ }
44
+ """
45
+
46
+ HTMLPAGE = """
47
+ <html>
48
+ <body>
49
+ <p id="p1"></p>
50
+ <script>
51
+ {}
52
+ </script>
53
+ </body>
54
+ </html>
55
+ """
42
56
 
43
57
 
44
58
  class MetadataKakao:
45
- @staticmethod
46
- def get_info_from_share_link(url: str) -> Tuple[Optional[str], Optional[str]]:
47
- headers = {"User-Agent": "Android"}
48
-
49
- response = requests.get(url, headers=headers)
50
- soup = BeautifulSoup(response.content.decode("utf-8", "ignore"), "html.parser")
51
-
52
- pack_title_tag = soup.find("title") # type: ignore
53
- if not pack_title_tag:
54
- return None, None
55
-
56
- pack_title: str = pack_title_tag.string # type: ignore
57
-
58
- app_scheme_link_tag = soup.find("a", id="app_scheme_link") # type: ignore
59
- assert isinstance(app_scheme_link_tag, Tag)
60
-
61
- item_code_fake = cast(str, app_scheme_link_tag["data-i"])
62
-
63
- js = ""
64
- for script_tag in soup.find_all("script"):
65
- js = script_tag.string
66
- if js and "emoticonDeepLink" in js:
67
- break
68
- if "emoticonDeepLink" not in js:
69
- return None, None
70
-
71
- func_start_pos = js.find("function emoticonDeepLink(")
72
- js = js[func_start_pos:]
73
- bracket_start_pos = js.find("{")
74
- func_end_pos = search_bracket(js[bracket_start_pos:]) + bracket_start_pos
75
- js = js[bracket_start_pos + 1 : func_end_pos]
76
- js = js.split(";")[0]
77
-
78
- minus_num_regex = re.search(r"\-(.*?)\^", js)
79
- if not minus_num_regex:
80
- return None, None
81
- minus_num_str = minus_num_regex.group(1)
82
- if not minus_num_str.isnumeric():
83
- return None, None
84
- minus_num = int(minus_num_str)
85
-
86
- xor_num_regex = re.search(r"\^(.*?)\)", js)
87
- if not xor_num_regex:
88
- return None, None
89
- xor_num_str = xor_num_regex.group(1)
90
- if not xor_num_str.isnumeric():
91
- return None, None
92
- xor_num = int(xor_num_str)
93
-
94
- item_code = str(int(item_code_fake) - minus_num ^ xor_num)
95
-
96
- # https://github.com/Nuitka/Nuitka/issues/385
97
- # js2py not working if compiled by nuitka
98
- # web2app_start_pos = js.find("daumtools.web2app(")
99
- # js = js[:web2app_start_pos] + "return a;}"
100
- # get_item_code = js2py.eval_js(js) # type: ignore
101
- # kakao_scheme_link = cast(
102
- # str,
103
- # get_item_code(
104
- # "kakaotalk://store/emoticon/${i}?referer=share_link", item_code_fake
105
- # ),
106
- # )
107
- # item_code = urlparse(kakao_scheme_link).path.split("/")[-1]
108
-
109
- return pack_title, item_code
110
-
111
59
  @staticmethod
112
60
  def get_item_code(title_ko: str, auth_token: str) -> Optional[str]:
113
61
  headers = {
@@ -177,15 +125,67 @@ class DownloadKakao(DownloadBase):
177
125
  self.pack_info_unauthed: Optional[dict[str, Any]] = None
178
126
  self.pack_info_authed: Optional[dict[str, Any]] = None
179
127
 
128
+ def get_info_from_share_link(self, url: str) -> Tuple[Optional[str], Optional[str]]:
129
+ headers = {"User-Agent": "Android"}
130
+
131
+ response = requests.get(url, headers=headers)
132
+ soup = BeautifulSoup(response.content.decode("utf-8", "ignore"), "html.parser")
133
+
134
+ pack_title_tag = soup.find("title") # type: ignore
135
+ if not pack_title_tag:
136
+ return None, None
137
+
138
+ pack_title: str = pack_title_tag.string # type: ignore
139
+
140
+ js = ""
141
+ for script_tag in soup.find_all("script"):
142
+ js = script_tag.string
143
+ if js and "daumtools.web2app" in js:
144
+ break
145
+ if "daumtools.web2app" not in js:
146
+ return None, None
147
+
148
+ js = js.replace(
149
+ "daumtools.web2app",
150
+ 'document.getElementById("p1").innerHTML = daumtools.web2app',
151
+ )
152
+ js = JSINJECT + js
153
+
154
+ with TemporaryDirectory() as tempdir:
155
+ html_page_path = Path(tempdir, "page.html")
156
+ html_page = HTMLPAGE.format(js)
157
+ with open(html_page_path, "w+") as f:
158
+ f.write(html_page)
159
+
160
+ try:
161
+ chromedriver_autoinstaller.install()
162
+ chrome_options = Options()
163
+ chrome_options.add_argument("--headless") # type: ignore
164
+ driver = webdriver.Chrome(options=chrome_options)
165
+ driver.get(html_page_path.as_posix())
166
+ wait = WebDriverWait(driver, 10)
167
+ wait.until(EC.text_to_be_present_in_element((By.ID, "p1"), "kakaotalk")) # type: ignore
168
+ kakao_url_elm = driver.find_element(By.ID, "p1")
169
+ kakao_url = cast(str, kakao_url_elm.text) # type: ignore
170
+ driver.close()
171
+ except ValueError:
172
+ webbrowser.open(html_page_path.as_posix())
173
+ prompt = "Chrome not installed, using manual method.\n"
174
+ prompt += "Please copy and paste the url you see in the browser"
175
+ self.cb.put(("ask_str", (prompt,), None))
176
+ kakao_url = cast(str, self.cb_return.get_response())
177
+
178
+ item_code = urlparse(kakao_url).path.split("/")[2]
179
+
180
+ return pack_title, item_code
181
+
180
182
  def download_stickers_kakao(self) -> bool:
181
183
  self.auth_token = None
182
184
  if self.opt_cred:
183
185
  self.auth_token = self.opt_cred.kakao_auth_token
184
186
 
185
187
  if urlparse(self.url).netloc == "emoticon.kakao.com":
186
- self.pack_title, item_code = MetadataKakao.get_info_from_share_link(
187
- self.url
188
- )
188
+ self.pack_title, item_code = self.get_info_from_share_link(self.url)
189
189
 
190
190
  if item_code:
191
191
  return self.download_animated(item_code)
@@ -268,14 +268,6 @@ class DownloadKakao(DownloadBase):
268
268
  self.out_dir, title=self.pack_title, author=self.author
269
269
  )
270
270
 
271
- success = self.download_animated_zip(item_code)
272
- if not success:
273
- self.cb.put("Trying to download one by one")
274
- success = self.download_animated_files(item_code)
275
-
276
- return success
277
-
278
- def download_animated_files(self, item_code: str) -> bool:
279
271
  play_exts = [".webp", ".gif", ".png", ""]
280
272
  play_types = ["emot", "emoji", ""] # emot = normal; emoji = mini
281
273
  play_path_format = None
@@ -376,46 +368,6 @@ class DownloadKakao(DownloadBase):
376
368
 
377
369
  return True
378
370
 
379
- def download_animated_zip(self, item_code: str) -> bool:
380
- pack_url = f"http://item.kakaocdn.net/dw/{item_code}.file_pack.zip"
381
-
382
- zip_file = self.download_file(pack_url)
383
- if zip_file:
384
- self.cb.put(f"Downloaded {pack_url}")
385
- else:
386
- self.cb.put(f"Cannot download {pack_url}")
387
- return False
388
-
389
- with zipfile.ZipFile(BytesIO(zip_file)) as zf:
390
- self.cb.put("Unzipping...")
391
- self.cb.put(
392
- (
393
- "bar",
394
- None,
395
- {"set_progress_mode": "determinate", "steps": len(zf.namelist())},
396
- )
397
- )
398
-
399
- for num, f_path in enumerate(sorted(zf.namelist())):
400
- ext = Path(f_path).suffix
401
-
402
- if ext in (".gif", ".webp"):
403
- data = DecryptKakao.xor_data(zf.read(f_path))
404
- self.cb.put(f"Decrypted {f_path}")
405
- else:
406
- data = zf.read(f_path)
407
- self.cb.put(f"Read {f_path}")
408
-
409
- out_path = Path(self.out_dir, str(num).zfill(3) + ext)
410
- with open(out_path, "wb") as f:
411
- f.write(data)
412
-
413
- self.cb.put("update_bar")
414
-
415
- self.cb.put(f"Finished getting {pack_url}")
416
-
417
- return True
418
-
419
371
  @staticmethod
420
372
  def start(
421
373
  url: str,
@@ -181,3 +181,5 @@ class CredFrame(LabelFrame):
181
181
  self.kakao_get_auth_btn.config(state=state)
182
182
  self.line_cookies_entry.config(state=state)
183
183
  self.line_get_auth_btn.config(state=state)
184
+ self.viber_auth_lbl.config(state=state)
185
+ self.viber_auth_entry.config(state=state)
@@ -51,11 +51,17 @@ class ViberGetAuthWindow(BaseWindow):
51
51
  justify="left",
52
52
  anchor="w",
53
53
  )
54
+ else:
55
+ self.explanation_lbl2 = Label(
56
+ self.frame_info,
57
+ text="You may be asked for admin password.",
58
+ justify="left",
59
+ anchor="w",
60
+ )
54
61
 
55
62
  self.explanation_lbl0.grid(column=0, row=0, sticky="w", padx=3, pady=3)
56
63
  self.explanation_lbl1.grid(column=0, row=1, sticky="w", padx=3, pady=3)
57
- if self.explanation_lbl2 is not None:
58
- self.explanation_lbl2.grid(column=0, row=2, sticky="w", padx=3, pady=3)
64
+ self.explanation_lbl2.grid(column=0, row=2, sticky="w", padx=3, pady=3)
59
65
 
60
66
  # Start button frame
61
67
  self.launch_btn = Button(
@@ -0,0 +1,8 @@
1
+ param($processId)
2
+ $dumpFilePath = "$Env:TEMP\memdump.bin.$processId"
3
+
4
+ Powershell -c rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump $processId $dumpFilePath full
5
+
6
+ icacls $dumpFilePath /remove "Everyone"
7
+ icacls $dumpFilePath /grant "Everyone:(r)"
8
+ icacls $dumpFilePath /grant "Everyone:(w)"
@@ -1,137 +1,183 @@
1
1
  #!/usr/bin/env python3
2
+ import importlib.util
2
3
  import os
3
4
  import platform
4
5
  import shutil
6
+ import signal
5
7
  import subprocess
6
8
  import time
7
9
  from getpass import getpass
8
10
  from pathlib import Path
9
- from typing import Callable, Optional, Tuple, cast
11
+ from typing import Callable, List, Optional, Tuple, cast
10
12
 
11
13
  from sticker_convert.definitions import ROOT_DIR
12
14
 
13
- # psutil is missing on arm64 linux appimage
14
- # Note: There is no Viber Desktop on arm64 linux anyway
15
- try:
16
- import psutil
17
-
18
- PSUTIL_LOADED = True
19
- except ModuleNotFoundError:
20
- PSUTIL_LOADED = False # type: ignore
21
-
22
15
  MSG_NO_BIN = """Viber Desktop not detected.
23
16
  Download and install Viber Desktop,
24
17
  then login to Viber Desktop and try again."""
25
-
26
18
  MSG_NO_AUTH = """Viber Desktop installed,
27
19
  but viber_auth not found.
28
20
  Please login to Viber Desktop and try again."""
29
-
30
21
  MSG_SIP_ENABLED = """You need to disable SIP:
31
22
  1. Restart computer in Recovery mode
32
23
  2. Launch Terminal from the Utilities menu
33
24
  3. Run the command `csrutil disable`
34
25
  4. Restart your computer"""
26
+ MSG_NO_PGREP = "pgrep command or psutil python package is necessary"
27
+ MSG_LAUNCH_FAIL = "Failed to launch Viber"
28
+ MSG_PERMISSION_ERROR = "Failed to read Viber process memory"
35
29
 
36
- MSG_NO_PSUTIL = "Python package psutil is necessary"
37
30
 
38
-
39
- def killall(name: str) -> bool:
40
- if not PSUTIL_LOADED:
31
+ def check_admin_windows() -> bool:
32
+ username = os.getenv("username")
33
+ if username is None:
41
34
  return False
42
35
 
36
+ s = subprocess.run(
37
+ ["net", "user", username],
38
+ capture_output=True,
39
+ text=True,
40
+ ).stdout
41
+
42
+ return True if "*Administrators" in s else False
43
+
44
+
45
+ def check_admin_linux() -> bool:
46
+ s = subprocess.run(
47
+ ["sudo", "-l"],
48
+ capture_output=True,
49
+ text=True,
50
+ ).stdout
51
+
52
+ return True if "may run the following commands" in s else False
53
+
54
+
55
+ def killall(name: str) -> bool:
43
56
  result = False
44
57
 
45
- for proc in psutil.process_iter(): # type: ignore
46
- if name in proc.name().lower():
47
- proc.kill()
58
+ while True:
59
+ pid = find_pid_by_name(name)
60
+ if pid is not None:
61
+ os.kill(pid, signal.SIGTERM)
48
62
  result = True
63
+ else:
64
+ break
49
65
 
50
66
  return result
51
67
 
52
68
 
53
69
  def find_pid_by_name(name: str) -> Optional[int]:
54
- if not PSUTIL_LOADED:
70
+ if platform.system() == "Windows":
71
+ s = subprocess.run(
72
+ ["powershell", "-c", "Get-Process", f"*{name}*"],
73
+ capture_output=True,
74
+ text=True,
75
+ ).stdout
76
+
77
+ for line in s.split("\n"):
78
+ if name in line.lower():
79
+ info = name.split()
80
+ pid = info[5]
81
+ if pid.isnumeric():
82
+ return int(pid)
83
+
55
84
  return None
85
+ else:
86
+ if platform.system() == "Darwin":
87
+ pattern = "Viber"
88
+ else:
89
+ pattern = ".*[Vv]iber.*"
90
+ pid = (
91
+ subprocess.run(["pgrep", pattern], capture_output=True, text=True)
92
+ .stdout.split("\n")[0]
93
+ .strip()
94
+ )
95
+ if pid == "" or pid.isnumeric() is False:
96
+ return None
97
+ else:
98
+ return int(pid)
99
+
100
+
101
+ if importlib.util.find_spec("psutil"):
102
+ import psutil
103
+
104
+ def killall(name: str) -> bool:
105
+ result = False
56
106
 
57
- for proc in psutil.process_iter(): # type: ignore
58
- if name in proc.name().lower():
59
- return proc.pid
107
+ for proc in psutil.process_iter(): # type: ignore
108
+ if name in proc.name().lower():
109
+ proc.kill()
110
+ result = True
60
111
 
61
- return None
112
+ return result
113
+
114
+ def find_pid_by_name(name: str) -> Optional[int]:
115
+ for proc in psutil.process_iter(): # type: ignore
116
+ if name in proc.name().lower():
117
+ return proc.pid
118
+
119
+ return None
62
120
 
63
121
 
64
122
  class GetViberAuth:
65
123
  def __init__(self, cb_ask_str: Callable[..., str] = input):
66
124
  self.cb_ask_str = cb_ask_str
67
125
 
68
- def get_auth_windows(self, viber_bin_path: str) -> Tuple[Optional[str], str]:
69
- if not PSUTIL_LOADED:
70
- return None, MSG_NO_PSUTIL
71
-
72
- member_id = None
73
- m_token = None
74
- m_ts = None
75
-
126
+ def relaunch_viber(self, viber_bin_path: str) -> Optional[int]:
76
127
  killed = killall("viber")
77
128
  if killed:
78
129
  time.sleep(5)
79
- subprocess.Popen([viber_bin_path])
130
+
131
+ if platform.system() == "Darwin":
132
+ cmd = ["open", "-n", viber_bin_path]
133
+ else:
134
+ cmd = [viber_bin_path]
135
+ subprocess.Popen(cmd)
80
136
  time.sleep(10)
81
137
 
82
- from PyMemoryEditor import OpenProcess # type: ignore
138
+ return find_pid_by_name("viber")
83
139
 
84
- viber_pid = find_pid_by_name("viber")
85
- with OpenProcess(pid=viber_pid) as process:
86
- for address in process.search_by_value(str, 18, "X-Viber-Auth-Mid: "): # type: ignore
87
- member_id_addr = cast(int, address) + 18
88
- member_id_bytes = process.read_process_memory(member_id_addr, bytes, 20)
89
- member_id_term = member_id_bytes.find(b"\x0d\x0a")
90
- if member_id_term == -1:
91
- continue
92
- member_id = member_id_bytes[:member_id_term].decode(encoding="ascii")
93
- break
94
- if member_id is None:
95
- return None, MSG_NO_AUTH
140
+ def get_mem_windows(self, viber_pid: int) -> Tuple[Optional[bytes], str]:
141
+ from pathlib import WindowsPath
96
142
 
97
- for address in process.search_by_value(str, 20, "X-Viber-Auth-Token: "): # type: ignore
98
- m_token_addr = cast(int, address) + 20
99
- m_token = process.read_process_memory(m_token_addr, str, 64)
100
- break
101
- if m_token is None:
102
- return None, MSG_NO_AUTH
103
-
104
- for address in process.search_by_value(str, 24, "X-Viber-Auth-Timestamp: "): # type: ignore
105
- m_ts_addr = cast(int, address) + 24
106
- m_ts = process.read_process_memory(m_ts_addr, str, 13)
143
+ memdump_ps_path = str(WindowsPath(ROOT_DIR / "resources/memdump_windows.ps1"))
144
+ arglist = (
145
+ f'-NoProfile -ExecutionPolicy Bypass -File "{memdump_ps_path}" {viber_pid}'
146
+ )
147
+ dump_fpath = os.path.expandvars(f"%temp%/memdump.bin.{viber_pid}")
148
+
149
+ cmd = [
150
+ "powershell",
151
+ "-NoProfile",
152
+ "-ExecutionPolicy",
153
+ "Bypass",
154
+ "-Command",
155
+ f"Start-Process -Verb RunAs powershell -ArgumentList '{arglist}'",
156
+ ]
157
+
158
+ subprocess.run(cmd, capture_output=True, text=True)
159
+
160
+ while True:
161
+ try:
162
+ with open(dump_fpath, "rb") as f:
163
+ s = f.read()
164
+ if len(s) != 0:
165
+ break
166
+ time.sleep(1)
167
+ except (FileNotFoundError, PermissionError):
168
+ pass
169
+
170
+ while True:
171
+ try:
172
+ os.remove(dump_fpath)
107
173
  break
108
- if m_ts is None:
109
- return None, MSG_NO_AUTH
110
-
111
- killall("viber")
112
-
113
- viber_auth = f"member_id:{member_id};m_token:{m_token};m_ts:{m_ts}"
114
- msg = "Got viber_auth successfully:\n"
115
- msg += f"{viber_auth=}\n"
116
-
117
- return viber_auth, msg
118
-
119
- def get_auth_linux(self, viber_bin_path: str) -> Tuple[Optional[str], str]:
120
- if not PSUTIL_LOADED:
121
- return None, MSG_NO_PSUTIL
122
-
123
- member_id = None
124
- m_token = None
125
- m_ts = None
174
+ except PermissionError:
175
+ pass
126
176
 
127
- killed = killall("viber")
128
- if killed:
129
- time.sleep(5)
130
- subprocess.Popen([viber_bin_path])
131
- time.sleep(10)
177
+ return s, ""
132
178
 
133
- viber_pid = find_pid_by_name("viber")
134
- memdump_sh_path = (ROOT_DIR / "resources/memdump.sh").as_posix()
179
+ def get_mem_linux(self, viber_pid: int) -> Tuple[Optional[bytes], str]:
180
+ memdump_sh_path = (ROOT_DIR / "resources/memdump_linux.sh").as_posix()
135
181
 
136
182
  s = subprocess.run(
137
183
  [
@@ -141,7 +187,7 @@ class GetViberAuth:
141
187
  capture_output=True,
142
188
  ).stdout
143
189
 
144
- if s.find(b"X-Viber-Auth-Mid: ") != -1:
190
+ if len(s) > 1000:
145
191
  pass
146
192
  elif shutil.which("pkexec") and os.getenv("DISPLAY"):
147
193
  s = subprocess.run(
@@ -174,27 +220,80 @@ class GetViberAuth:
174
220
  stdin=sudo_password_pipe.stdout,
175
221
  ).stdout
176
222
 
177
- member_id_addr = s.find(b"X-Viber-Auth-Mid: ")
178
- m_token_addr = s.find(b"X-Viber-Auth-Token: ")
179
- m_ts_addr = s.find(b"X-Viber-Auth-Timestamp: ")
223
+ return s, ""
180
224
 
181
- if member_id_addr == -1 or m_token_addr == -1 or m_ts_addr == -1:
182
- return None, MSG_NO_AUTH
225
+ def get_mem_darwin(self, viber_pid: int) -> Tuple[Optional[bytes], str]:
226
+ subprocess.run(
227
+ [
228
+ "lldb",
229
+ "--attach-pid",
230
+ str(viber_pid),
231
+ "-o",
232
+ "process save-core /tmp/viber.dmp",
233
+ "-o",
234
+ "quit",
235
+ ],
236
+ stdout=subprocess.DEVNULL,
237
+ stderr=subprocess.DEVNULL,
238
+ )
183
239
 
184
- member_id_addr += 18
185
- m_token_addr += 20
186
- m_ts_addr += 24
240
+ with open("/tmp/viber.dmp", "rb") as f:
241
+ s = f.read()
187
242
 
188
- member_id_bytes = s[member_id_addr : member_id_addr + 20]
189
- member_id_term = member_id_bytes.find(b"\x0d\x0a")
190
- if member_id_term == -1:
191
- return None, MSG_NO_AUTH
192
- member_id = member_id_bytes[:member_id_term].decode(encoding="ascii")
243
+ os.remove("/tmp/viber.dmp")
193
244
 
194
- m_token = s[m_token_addr : m_token_addr + 64].decode(encoding="ascii")
195
- m_ts = s[m_ts_addr : m_ts_addr + 13].decode(encoding="ascii")
245
+ return s, ""
196
246
 
197
- killall("viber")
247
+ def get_auth_by_pme(
248
+ self, viber_bin_path: str, relaunch: bool = True
249
+ ) -> Tuple[Optional[str], str]:
250
+ from PyMemoryEditor import OpenProcess # type: ignore
251
+
252
+ member_id = None
253
+ m_token = None
254
+ m_ts = None
255
+
256
+ if relaunch:
257
+ viber_pid = self.relaunch_viber(viber_bin_path)
258
+ else:
259
+ viber_pid = find_pid_by_name("viber")
260
+ if viber_pid is None:
261
+ return None, MSG_LAUNCH_FAIL
262
+
263
+ try:
264
+ with OpenProcess(pid=int(viber_pid)) as process:
265
+ for address in process.search_by_value(str, 18, "X-Viber-Auth-Mid: "): # type: ignore
266
+ member_id_addr = cast(int, address) + 18
267
+ member_id_bytes = process.read_process_memory(
268
+ member_id_addr, bytes, 20
269
+ )
270
+ member_id_term = member_id_bytes.find(b"\x0d\x0a")
271
+ if member_id_term == -1:
272
+ continue
273
+ member_id = member_id_bytes[:member_id_term].decode(
274
+ encoding="ascii"
275
+ )
276
+ break
277
+ if member_id is None:
278
+ return None, MSG_NO_AUTH
279
+
280
+ for address in process.search_by_value(str, 20, "X-Viber-Auth-Token: "): # type: ignore
281
+ m_token_addr = cast(int, address) + 20
282
+ m_token = process.read_process_memory(m_token_addr, str, 64)
283
+ break
284
+ if m_token is None:
285
+ return None, MSG_NO_AUTH
286
+
287
+ for address in process.search_by_value( # type: ignore
288
+ str, 24, "X-Viber-Auth-Timestamp: "
289
+ ):
290
+ m_ts_addr = cast(int, address) + 24
291
+ m_ts = process.read_process_memory(m_ts_addr, str, 13)
292
+ break
293
+ if m_ts is None:
294
+ return None, MSG_NO_AUTH
295
+ except PermissionError:
296
+ return None, MSG_PERMISSION_ERROR
198
297
 
199
298
  viber_auth = f"member_id:{member_id};m_token:{m_token};m_ts:{m_ts}"
200
299
  msg = "Got viber_auth successfully:\n"
@@ -202,50 +301,43 @@ class GetViberAuth:
202
301
 
203
302
  return viber_auth, msg
204
303
 
205
- def get_auth_darwin(self, viber_bin_path: str) -> Tuple[Optional[str], str]:
304
+ def get_auth_by_dump(
305
+ self, viber_bin_path: str, relaunch: bool = True
306
+ ) -> Tuple[Optional[str], str]:
206
307
  member_id = None
207
308
  m_token = None
208
309
  m_ts = None
209
310
 
210
- csrutil_status = subprocess.run(
211
- ["csrutil", "status"], capture_output=True, text=True
212
- ).stdout
213
-
214
- if "enabled" in csrutil_status:
215
- return None, MSG_SIP_ENABLED
216
-
217
- killed = killall("viber")
218
- if killed:
219
- time.sleep(5)
220
- subprocess.run(
221
- ["open", "-n", viber_bin_path],
222
- stdout=subprocess.DEVNULL,
223
- stderr=subprocess.DEVNULL,
224
- )
225
- time.sleep(10)
311
+ if platform.system() == "Darwin":
312
+ csrutil_status = subprocess.run(
313
+ ["csrutil", "status"], capture_output=True, text=True
314
+ ).stdout
226
315
 
227
- viber_pid = subprocess.run(
228
- ["pgrep", "Viber"], capture_output=True, text=True
229
- ).stdout.strip()
230
- subprocess.run(
231
- [
232
- "lldb",
233
- "--attach-pid",
234
- viber_pid,
235
- "-o",
236
- "process save-core /tmp/viber.dmp",
237
- "-o",
238
- "quit",
239
- ],
240
- stdout=subprocess.DEVNULL,
241
- stderr=subprocess.DEVNULL,
242
- )
316
+ if "enabled" in csrutil_status:
317
+ return None, MSG_SIP_ENABLED
318
+ elif (
319
+ platform.system() != "Windows"
320
+ and shutil.which("pgrep") is None
321
+ and importlib.find_loader("psutil") is None
322
+ ):
323
+ return None, MSG_NO_PGREP
324
+
325
+ if relaunch:
326
+ viber_pid = self.relaunch_viber(viber_bin_path)
327
+ else:
328
+ viber_pid = find_pid_by_name("viber")
329
+ if viber_pid is None:
330
+ return None, MSG_LAUNCH_FAIL
243
331
 
244
- with open("/tmp/viber.dmp", "rb") as f:
245
- s = f.read()
332
+ if platform.system() == "Windows":
333
+ s, msg = self.get_mem_windows(viber_pid)
334
+ elif platform.system() == "Darwin":
335
+ s, msg = self.get_mem_darwin(viber_pid)
336
+ else:
337
+ s, msg = self.get_mem_linux(viber_pid)
246
338
 
247
- os.remove("/tmp/viber.dmp")
248
- killall("viber")
339
+ if s is None:
340
+ return None, msg
249
341
 
250
342
  member_id_addr = s.find(b"X-Viber-Auth-Mid: ")
251
343
  m_token_addr = s.find(b"X-Viber-Auth-Token: ")
@@ -307,9 +399,43 @@ class GetViberAuth:
307
399
  if not viber_bin_path:
308
400
  return None, MSG_NO_BIN
309
401
 
310
- if platform.system() == "Windows":
311
- return self.get_auth_windows(viber_bin_path)
312
- elif platform.system() == "Darwin":
313
- return self.get_auth_darwin(viber_bin_path)
402
+ # get_auth_by_dump()
403
+ # + Fast
404
+ # - Requires admin
405
+
406
+ # get_auth_by_pme()
407
+ # + No admin (If have permission to read process)
408
+ # - Slow
409
+ # - Cannot run on macOS
410
+
411
+ # If admin, prefer get_auth_by_dump() over get_auth_by_pme(), vice versa
412
+ methods: List[Callable[[str, bool], Tuple[Optional[str], str]]] = []
413
+ relaunch = True
414
+ viber_auth = None
415
+ msg = ""
416
+
417
+ pme_present = importlib.util.find_spec("PyMemoryEditor") is not None
418
+ if platform.system() == "Darwin":
419
+ methods.append(self.get_auth_by_dump)
420
+ elif platform.system() == "Windows":
421
+ methods.append(self.get_auth_by_dump)
422
+ if pme_present:
423
+ methods.append(self.get_auth_by_pme)
424
+ if check_admin_windows() is False:
425
+ methods.reverse()
314
426
  else:
315
- return self.get_auth_linux(viber_bin_path)
427
+ if not os.path.isfile("/.dockerenv"):
428
+ methods.append(self.get_auth_by_dump)
429
+ if pme_present:
430
+ methods.append(self.get_auth_by_pme)
431
+ if check_admin_linux() is False:
432
+ methods.reverse()
433
+
434
+ for method in methods:
435
+ viber_auth, msg = method(viber_bin_path, relaunch)
436
+ relaunch = False
437
+ if viber_auth is not None:
438
+ break
439
+
440
+ killall("viber")
441
+ return viber_auth, msg
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.9.0"
3
+ __version__ = "2.9.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sticker-convert
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -367,20 +367,22 @@ License-File: LICENSE
367
367
  Requires-Dist: aiolimiter ~=1.1.0
368
368
  Requires-Dist: anyio ~=4.4.0
369
369
  Requires-Dist: apngasm-python ~=1.3.1
370
- Requires-Dist: av ~=12.1.0
370
+ Requires-Dist: av ~=12.2.0
371
371
  Requires-Dist: beautifulsoup4 ~=4.12.3
372
- Requires-Dist: rookiepy ~=0.5.1
372
+ Requires-Dist: chromedriver-autoinstaller ~=0.6.4
373
+ Requires-Dist: rookiepy ~=0.5.2
373
374
  Requires-Dist: imagequant ~=1.1.1
374
375
  Requires-Dist: memory-tempfile ~=2.2.3
375
376
  Requires-Dist: mergedeep ~=1.3.4
376
377
  Requires-Dist: numpy >=1.22.4
377
- Requires-Dist: Pillow ~=10.3.0
378
+ Requires-Dist: Pillow ~=10.4.0
378
379
  Requires-Dist: pyoxipng ~=9.0.0
379
- Requires-Dist: python-telegram-bot ~=21.2
380
- Requires-Dist: psutil ~=5.9.8
380
+ Requires-Dist: python-telegram-bot ~=21.3
381
+ Requires-Dist: psutil ~=6.0.0
381
382
  Requires-Dist: PyMemoryEditor ~=1.5.22
382
383
  Requires-Dist: requests ~=2.32.3
383
- Requires-Dist: rlottie-python ~=1.3.5
384
+ Requires-Dist: rlottie-python ~=1.3.6
385
+ Requires-Dist: selenium ~=4.22.0
384
386
  Requires-Dist: signalstickers-client-fork-laggykiller ~=3.3.0.post2
385
387
  Requires-Dist: sqlcipher3-wheels ~=0.5.2.post1
386
388
  Requires-Dist: tqdm ~=4.66.4
@@ -392,7 +394,7 @@ Requires-Dist: ttkbootstrap-fork-laggykiller ~=1.5.1
392
394
 
393
395
  - A python script for creating, downloading, converting+compressing and uploading stickers from multiple instant messaging applications.
394
396
  - With GUI and CLI that runs on Windows, MacOS and Linux
395
- - Currently supports Signal, Telegram, WhatsApp (Create .wastickers), Line (Download only), Kakao (Download only), Viber (Download only), iMessage (Create Xcode sticker pack project)
397
+ - Currently supports Signal, Telegram, WhatsApp (Create .wastickers), Line (Download only), Kakao (Download only), Viber, iMessage (Create Xcode sticker pack project)
396
398
  - Supports static and animated stickers, with transparency support
397
399
 
398
400
  ## Downloads
@@ -430,7 +432,7 @@ Requires-Dist: ttkbootstrap-fork-laggykiller ~=1.5.1
430
432
  | [WhatsApp](docs/guide_whatsapp.md) | ⭕ (By Android or WhatsApp Web) | ⭕ (Create `.wastickers`, import by Sticker Maker) |
431
433
  | [Line](docs/guide_line.md) | ✅ | 🚫 (Need to submit for manual approval) |
432
434
  | [Kakao](docs/guide_kakao.md) | ✅ (Need 'share link' for animated) | 🚫 (Need to submit for manual approval) |
433
- | [Viber](docs/guide_viber.md) | ✅ | 🚫 (Manually upload through Viber app) |
435
+ | [Viber](docs/guide_viber.md) | ✅ | (Require `viber_auth`) |
434
436
  | [iMessage](docs/guide_imessage.md) | 🚫 | ⭕ (Create Xcode stickerpack project for sideload) |
435
437
 
436
438
  ✅ = Supported ⭕ = Partially supported 🚫 = Not supported
@@ -459,7 +461,7 @@ Requires-Dist: ttkbootstrap-fork-laggykiller ~=1.5.1
459
461
  - Upload: Not supported. You need to manually submit sticker pack for approval before you can use in app.
460
462
  - Viber
461
463
  - Download: Supported (e.g. `https://stickers.viber.com/pages/example` OR `https://stickers.viber.com/pages/custom-sticker-packs/example`)
462
- - Upload: The program can convert images to png with 490x490 for uploading to viber manually. It should be noted that Viber is able to resize images for you, so it may not be necessary to use sticker-convert for creating Viber sticker pack.
464
+ - Upload: Supported. Viber authentication data required for uploading Viber stickers, which could be fetched from Viber Desktop application automatically.
463
465
  - iMessage
464
466
  - Download: Not supported.
465
467
  - Upload: The program can create Xcode project for iMessage sticker pack, which could then be compiled and sideloaded using Xcode.
@@ -6,10 +6,10 @@ sticker_convert/definitions.py,sha256=ZhP2ALCEud-w9ZZD4c3TDG9eHGPZyaAL7zPUsJAbjt
6
6
  sticker_convert/gui.py,sha256=hZOp2SyEYKbCRm9SRTItBqEk724SHtSqK_Txo0wpL8Q,31194
7
7
  sticker_convert/job.py,sha256=4C2WwB3PFldacqI8UPnM10thM087VLEl9wn6P9_LtS0,26128
8
8
  sticker_convert/job_option.py,sha256=yFwHEhW8Gzp9dfiXakkCREfejAIJhiOxwD4Wlg9jcPk,7805
9
- sticker_convert/version.py,sha256=UUJ5qrYrmZsock_EC-nYp5wKxs3ZAkgVkQPLxSv6l1E,46
9
+ sticker_convert/version.py,sha256=KtHdsdkHNKLnGLbDOZKFG0OukVKP9IeWcQlHYhF_lg0,46
10
10
  sticker_convert/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- sticker_convert/downloaders/download_base.py,sha256=x18bI2mPpbXRnSmStBHEb1IvN-VPCilOHLQUs6YPUEU,4041
12
- sticker_convert/downloaders/download_kakao.py,sha256=TCygK-LiSkk3yEYZymZsHVwtW5JmQX6JeIneh25XxFA,14984
11
+ sticker_convert/downloaders/download_base.py,sha256=WGEtri16p6GCG7hhQ603pKS8efoB72oCBHTuJ_RVmG0,4041
12
+ sticker_convert/downloaders/download_kakao.py,sha256=msUUUR-2JdeAO_pQPJBFuA9xVfh1Gwl19WYN_6WLA-Y,13324
13
13
  sticker_convert/downloaders/download_line.py,sha256=9WzOWujTbZdAqBi52k21OUEfRmcV1loCaJiDmg6dklw,17853
14
14
  sticker_convert/downloaders/download_signal.py,sha256=PfwscdbcEd_5C3Ecs0F8Qc8si1sLzLodAdnsHVwXgac,3063
15
15
  sticker_convert/downloaders/download_telegram.py,sha256=jufMqc78aXOPDr7fQf9ykkNyhQ7KVCp4gRBxs09NgMo,4614
@@ -20,7 +20,7 @@ sticker_convert/gui_components/frames/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
20
20
  sticker_convert/gui_components/frames/comp_frame.py,sha256=9k_UntKKi2G_g0byzoj1rdTqOq7q9mcnXiy799bYnr0,7257
21
21
  sticker_convert/gui_components/frames/config_frame.py,sha256=b3X4QAnGpde0OhthXHmjSyU_Yb5tYRUFXmy04Yi8Zmo,4316
22
22
  sticker_convert/gui_components/frames/control_frame.py,sha256=_XiOJ9JPUfiysDghGG04sEVLrXG9TMVlDZ60W0LhYVI,835
23
- sticker_convert/gui_components/frames/cred_frame.py,sha256=QCAmDfSgL-ykGxPsL_9kWFssbtVdrh17IWW28oxzdDg,7429
23
+ sticker_convert/gui_components/frames/cred_frame.py,sha256=AJHxxBdEa-xOteevVBsSfAHtcJklU3pu2TqvZny9o4w,7527
24
24
  sticker_convert/gui_components/frames/input_frame.py,sha256=5Vz1d6J1jkofvvzm43DxeIW_CjWfxa2QPYFnkAAiAfM,5040
25
25
  sticker_convert/gui_components/frames/output_frame.py,sha256=n2WLk22h61DoZli8WbFhd-h2CqWAebDXnBa051JBuOc,4260
26
26
  sticker_convert/gui_components/frames/progress_frame.py,sha256=LWUZg_iL7iiNTfu7N5Ct_pklZdghxihENi7DP9YozOE,4915
@@ -31,7 +31,7 @@ sticker_convert/gui_components/windows/base_window.py,sha256=xBE1peGMPvWsdrFej0C
31
31
  sticker_convert/gui_components/windows/kakao_get_auth_window.py,sha256=AvXB2Q8pAPkKILHTvlLGV9jfBcvskCA4arko4nvBTdo,7115
32
32
  sticker_convert/gui_components/windows/line_get_auth_window.py,sha256=S4ES_lk2-GDvPokZtYALnUc5zW1VbS4WulNqO9K1aSs,3375
33
33
  sticker_convert/gui_components/windows/signal_get_auth_window.py,sha256=kbJPe558lLP4PbPT2mCw4Xgh06FX5mBEZXoW-Q1LvWY,4584
34
- sticker_convert/gui_components/windows/viber_get_auth_window.py,sha256=_LOAhyNEp4OL24i_PsVtXYE01EArH-9gOOdEaFG4H30,4876
34
+ sticker_convert/gui_components/windows/viber_get_auth_window.py,sha256=Ne0DG4wog7Pzw-fPsbr7HX40okFFo1INusVCSwSAQdQ,5051
35
35
  sticker_convert/ios-message-stickers-template/.gitignore,sha256=4uuTph_9eHfqXHUavLOmGOji6aIHOif2bUEU_hCBn4Y,9
36
36
  sticker_convert/ios-message-stickers-template/README.md,sha256=oN0FvJkCWWjSZ3PMrCvY3T1zCsdkZYFgGHAoFh0Kmt8,467
37
37
  sticker_convert/ios-message-stickers-template/.github/FUNDING.yml,sha256=3LlmdSAGDsBA2o_C1iBYTNLwkABnyZuN0zxgPPyd-f8,70
@@ -72,7 +72,8 @@ sticker_convert/resources/compression.json,sha256=tNBGCmFqZ4Bsik1-xtvkOSlRu1uF6K
72
72
  sticker_convert/resources/emoji.json,sha256=sXSuKusyG1L2Stuf9BL5ZqfzOIOdeAeE3RXcrXAsLdY,413367
73
73
  sticker_convert/resources/help.json,sha256=VgRYw5iNQGn_CDA4pTnk5QUO21lsx0JIvWgUV2kHPvY,6906
74
74
  sticker_convert/resources/input.json,sha256=S3CkInBMLrk5OS9aJfuTCDsf_64NOjNT3IKQr7d1hM0,2665
75
- sticker_convert/resources/memdump.sh,sha256=YbdX5C5RyCnoeDUE6JgTo8nQXKhrUw5-kFDx5bQp9tY,651
75
+ sticker_convert/resources/memdump_linux.sh,sha256=YbdX5C5RyCnoeDUE6JgTo8nQXKhrUw5-kFDx5bQp9tY,651
76
+ sticker_convert/resources/memdump_windows.ps1,sha256=CfyNSSEW3HJOkTu-mKrP3qh5aprN-1VCBfj-R1fELA0,302
76
77
  sticker_convert/resources/output.json,sha256=V4OseXEm3O16cAjHDVZRq8-SY-0_7cFFR_cyF-0Y_eQ,1570
77
78
  sticker_convert/uploaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
79
  sticker_convert/uploaders/compress_wastickers.py,sha256=SMPf1_ir30ZKO2ChHspDFuyaufx0XeVBVLOlHmawEdY,6021
@@ -86,7 +87,7 @@ sticker_convert/utils/url_detect.py,sha256=L2QwE2jwN85MoyYsW_4GvBHuoedrlhoIdAmzw
86
87
  sticker_convert/utils/auth/get_kakao_auth.py,sha256=ipAZ1DUd5CMTpUoxRXHVOFC3DKIpxwxpTYAfrOJ6UZ8,9829
87
88
  sticker_convert/utils/auth/get_line_auth.py,sha256=8l8ha2vQmk3rHGvDE7PkcxQXbH3oe62LKbI3qVUtvqc,2196
88
89
  sticker_convert/utils/auth/get_signal_auth.py,sha256=6Sx-lMuyBHeX1RpjAWI8u03qnRu9fmO4p89pd7fowOE,4925
89
- sticker_convert/utils/auth/get_viber_auth.py,sha256=U3_8qxVifiZ7T47PMW6Cdo7CcLJnUW-_rf8QIYL9bYY,9984
90
+ sticker_convert/utils/auth/get_viber_auth.py,sha256=UUdnESATVGj-mrDuM_5wq75ouQFqPvFtdyqjGDqxi5Y,13734
90
91
  sticker_convert/utils/files/cache_store.py,sha256=etfe614OAhAyrnM5fGeESKq6R88YLNqkqkxSzEmZ0V0,1047
91
92
  sticker_convert/utils/files/json_manager.py,sha256=Vr6pZJdLMkrJJWN99210aduVHb0ILyf0SSTaw4TZqgc,541
92
93
  sticker_convert/utils/files/json_resources_loader.py,sha256=flZFixUXRTrOAhvRQpuSQgmJ69yXL94sxukcowLT1JQ,1049
@@ -97,9 +98,9 @@ sticker_convert/utils/media/apple_png_normalize.py,sha256=LbrQhc7LlYX4I9ek4XJsZE
97
98
  sticker_convert/utils/media/codec_info.py,sha256=1QfW3wgZ5vOk7T4XtLHYvJK1x8RbASRPSvhKEPkcu9A,15747
98
99
  sticker_convert/utils/media/decrypt_kakao.py,sha256=4wq9ZDRnFkx1WmFZnyEogBofiLGsWQM_X69HlA36578,1947
99
100
  sticker_convert/utils/media/format_verify.py,sha256=MH68GLJfXeL8WFT8emtj355K5BLAtUX64tQ59nugx2c,5673
100
- sticker_convert-2.9.0.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
101
- sticker_convert-2.9.0.dist-info/METADATA,sha256=aGTUdCCINVKxrj8kM3b5pMpMDKbVu51_uuvVR8ee_2A,51091
102
- sticker_convert-2.9.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
103
- sticker_convert-2.9.0.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
104
- sticker_convert-2.9.0.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
105
- sticker_convert-2.9.0.dist-info/RECORD,,
101
+ sticker_convert-2.9.2.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
102
+ sticker_convert-2.9.2.dist-info/METADATA,sha256=09ZbSV3QNFOUGbBUrWrWHZ6Qac7awAGPB5RAFpQiCTk,51071
103
+ sticker_convert-2.9.2.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
104
+ sticker_convert-2.9.2.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
105
+ sticker_convert-2.9.2.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
106
+ sticker_convert-2.9.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5