sticker-convert 2.9.4__py3-none-any.whl → 2.10.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/cli.py +26 -13
- sticker_convert/downloaders/download_base.py +20 -23
- sticker_convert/downloaders/download_discord.py +91 -0
- sticker_convert/downloaders/download_kakao.py +3 -4
- sticker_convert/downloaders/download_line.py +3 -4
- sticker_convert/downloaders/download_signal.py +3 -4
- sticker_convert/downloaders/download_telegram.py +3 -4
- sticker_convert/downloaders/download_viber.py +3 -4
- sticker_convert/gui.py +15 -3
- sticker_convert/gui_components/frames/cred_frame.py +22 -1
- sticker_convert/gui_components/windows/discord_get_auth_window.py +82 -0
- sticker_convert/gui_components/windows/signal_get_auth_window.py +39 -85
- sticker_convert/job.py +11 -1
- sticker_convert/job_option.py +2 -0
- sticker_convert/resources/compression.json +94 -0
- sticker_convert/resources/help.json +2 -1
- sticker_convert/resources/input.json +20 -0
- sticker_convert/uploaders/upload_signal.py +5 -4
- sticker_convert/uploaders/upload_telegram.py +11 -10
- sticker_convert/utils/auth/get_discord_auth.py +115 -0
- sticker_convert/utils/auth/get_signal_auth.py +115 -114
- sticker_convert/utils/auth/get_viber_auth.py +10 -214
- sticker_convert/utils/chrome_remotedebug.py +152 -0
- sticker_convert/utils/emoji.py +16 -0
- sticker_convert/utils/files/run_bin.py +1 -1
- sticker_convert/utils/media/codec_info.py +45 -60
- sticker_convert/utils/process.py +187 -0
- sticker_convert/utils/url_detect.py +3 -0
- sticker_convert/version.py +1 -1
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/METADATA +40 -42
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/RECORD +35 -29
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/LICENSE +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/WHEEL +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.0.dist-info}/top_level.txt +0 -0
@@ -3,127 +3,128 @@ import json
|
|
3
3
|
import os
|
4
4
|
import platform
|
5
5
|
import shutil
|
6
|
-
|
7
|
-
|
6
|
+
import time
|
7
|
+
import webbrowser
|
8
|
+
from typing import Callable, Optional, Tuple, cast
|
8
9
|
|
9
|
-
from
|
10
|
+
from sticker_convert.definitions import CONFIG_DIR
|
11
|
+
from sticker_convert.utils.chrome_remotedebug import CRD
|
12
|
+
from sticker_convert.utils.process import killall
|
10
13
|
|
11
14
|
|
12
15
|
class GetSignalAuth:
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
cb_msg: Callable[..., None] = print,
|
19
|
+
cb_ask_str: Callable[..., str] = input,
|
20
|
+
):
|
21
|
+
chromedriver_download_dir = CONFIG_DIR / "bin"
|
22
|
+
os.makedirs(chromedriver_download_dir, exist_ok=True)
|
23
|
+
|
24
|
+
self.chromedriver_download_dir = chromedriver_download_dir
|
25
|
+
|
26
|
+
self.cb_ask_str = cb_ask_str
|
27
|
+
self.cb_msg = cb_msg
|
28
|
+
|
29
|
+
def download_signal_desktop(self):
|
30
|
+
download_url = "https://signal.org/en/download/"
|
31
|
+
|
32
|
+
webbrowser.open(download_url)
|
33
|
+
|
34
|
+
self.cb_msg(download_url)
|
35
|
+
|
36
|
+
prompt = "Signal Desktop not detected.\n"
|
37
|
+
prompt += "Download and install Signal Desktop version\n"
|
38
|
+
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
|
23
42
|
)
|
24
|
-
|
25
|
-
|
43
|
+
else:
|
44
|
+
self.cb_msg(prompt)
|
45
|
+
|
46
|
+
def get_signal_bin_path(self) -> Optional[str]:
|
47
|
+
signal_paths: Tuple[Optional[str], ...]
|
48
|
+
if platform.system() == "Windows":
|
49
|
+
signal_paths = (
|
50
|
+
os.path.expandvars("%localappdata%/Programs/signal-desktop/Signal.exe"),
|
51
|
+
os.path.expandvars(
|
52
|
+
"%localappdata%/Programs/signal-desktop-beta/Signal Beta.exe"
|
53
|
+
),
|
26
54
|
)
|
27
55
|
elif platform.system() == "Darwin":
|
28
|
-
|
29
|
-
|
30
|
-
"/Applications/Signal Beta.app/Contents/MacOS/Signal Beta"
|
56
|
+
signal_paths = (
|
57
|
+
"/Applications/Signal.app/Contents/MacOS/Signal",
|
58
|
+
"/Applications/Signal Beta.app/Contents/MacOS/Signal Beta",
|
31
59
|
)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
"~/Library/Application Support/Signal Beta"
|
60
|
+
else:
|
61
|
+
signal_paths = (
|
62
|
+
shutil.which("signal-desktop"),
|
63
|
+
shutil.which("signal-desktop-beta"),
|
37
64
|
)
|
65
|
+
|
66
|
+
for signal_path in signal_paths:
|
67
|
+
if signal_path is not None and os.path.isfile(signal_path):
|
68
|
+
return signal_path
|
69
|
+
return None
|
70
|
+
|
71
|
+
def get_cred(self) -> Tuple[Optional[str], Optional[str]]:
|
72
|
+
signal_bin_path = self.get_signal_bin_path()
|
73
|
+
if signal_bin_path is None:
|
74
|
+
self.download_signal_desktop()
|
75
|
+
return None, None
|
76
|
+
|
77
|
+
if platform.system() == "Windows":
|
78
|
+
killall("signal")
|
38
79
|
else:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
signal_database = Path(signal_user_data_dir, "sql/db.sqlite")
|
91
|
-
|
92
|
-
if not signal_database.is_file():
|
93
|
-
msg = "Signal Desktop installed,\n"
|
94
|
-
msg += "but database file not found.\n"
|
95
|
-
msg += "Please login to Signal Desktop and try again.\n"
|
96
|
-
msg += "\n"
|
97
|
-
msg += f"{signal_bin_path=}\n"
|
98
|
-
msg += f"{signal_user_data_dir=}\n"
|
99
|
-
return None, None, msg
|
100
|
-
|
101
|
-
db_conn = sqlite3.connect(signal_database.as_posix())
|
102
|
-
db_cursor = db_conn.cursor()
|
103
|
-
db_cursor.execute(f'PRAGMA key="{db_key}"')
|
104
|
-
|
105
|
-
uuid_id = None
|
106
|
-
result = db_cursor.execute("SELECT * FROM items WHERE id='uuid_id'").fetchone()
|
107
|
-
if result:
|
108
|
-
uuid_id = json.loads(result[1])["value"]
|
109
|
-
|
110
|
-
password = None
|
111
|
-
result = db_cursor.execute("SELECT * FROM items WHERE id='password'").fetchone()
|
112
|
-
if result:
|
113
|
-
password = json.loads(result[1])["value"]
|
114
|
-
|
115
|
-
db_conn.close()
|
116
|
-
|
117
|
-
if uuid_id and password:
|
118
|
-
msg = "Got uuid and password successfully:\n"
|
119
|
-
msg += f"{uuid_id=}\n"
|
120
|
-
msg += f"{password=}"
|
121
|
-
return uuid_id, password, msg
|
122
|
-
|
123
|
-
msg = "Signal Desktop installed and Database found,\n"
|
124
|
-
msg += "but uuid and password not found.\n"
|
125
|
-
msg += "Please login to Signal Desktop and try again.\n"
|
126
|
-
msg += "\n"
|
127
|
-
msg += f"{signal_bin_path=}\n"
|
128
|
-
msg += f"{signal_user_data_dir=}\n"
|
129
|
-
return None, None, msg
|
80
|
+
killall("signal-desktop")
|
81
|
+
|
82
|
+
crd = CRD(signal_bin_path)
|
83
|
+
crd.connect()
|
84
|
+
# crd.runtime_enable()
|
85
|
+
# crd.reload()
|
86
|
+
# while True:
|
87
|
+
# r = crd.ws.recv()
|
88
|
+
# data = json.loads(r)
|
89
|
+
# if data.get("method") == "Runtime.executionContextCreated":
|
90
|
+
# print(data)
|
91
|
+
# if (data.get("method") == "Runtime.executionContextCreated" and
|
92
|
+
# data["params"]["context"]["name"] == "Electron Isolated Context"
|
93
|
+
# ):
|
94
|
+
# context_id = data["params"]["context"]["id"]
|
95
|
+
# break
|
96
|
+
# crd.runtime_disable()
|
97
|
+
context_id = 2
|
98
|
+
|
99
|
+
uuid, password = None, None
|
100
|
+
while True:
|
101
|
+
try:
|
102
|
+
r = crd.exec_js(
|
103
|
+
"window.reduxStore.getState().items.uuid_id", context_id
|
104
|
+
)
|
105
|
+
except RuntimeError:
|
106
|
+
break
|
107
|
+
if (
|
108
|
+
json.loads(r).get("result", {}).get("result", {}).get("type", "")
|
109
|
+
== "string"
|
110
|
+
):
|
111
|
+
uuid = cast(str, json.loads(r)["result"]["result"]["value"])
|
112
|
+
break
|
113
|
+
time.sleep(1)
|
114
|
+
while True:
|
115
|
+
try:
|
116
|
+
r = crd.exec_js(
|
117
|
+
"window.reduxStore.getState().items.password", context_id
|
118
|
+
)
|
119
|
+
except RuntimeError:
|
120
|
+
break
|
121
|
+
if (
|
122
|
+
json.loads(r).get("result", {}).get("result", {}).get("type", "")
|
123
|
+
== "string"
|
124
|
+
):
|
125
|
+
password = cast(str, json.loads(r)["result"]["result"]["value"])
|
126
|
+
break
|
127
|
+
time.sleep(1)
|
128
|
+
|
129
|
+
crd.close()
|
130
|
+
return uuid, password
|
@@ -3,14 +3,14 @@ import importlib.util
|
|
3
3
|
import os
|
4
4
|
import platform
|
5
5
|
import shutil
|
6
|
-
import signal
|
7
6
|
import subprocess
|
8
7
|
import time
|
8
|
+
from functools import partial
|
9
9
|
from getpass import getpass
|
10
10
|
from pathlib import Path
|
11
11
|
from typing import Callable, List, Optional, Tuple, cast
|
12
12
|
|
13
|
-
from sticker_convert.
|
13
|
+
from sticker_convert.utils.process import check_admin, find_pid_by_name, get_mem, killall
|
14
14
|
|
15
15
|
MSG_NO_BIN = """Viber Desktop not detected.
|
16
16
|
Download and install Viber Desktop,
|
@@ -23,102 +23,10 @@ MSG_SIP_ENABLED = """You need to disable SIP:
|
|
23
23
|
2. Launch Terminal from the Utilities menu
|
24
24
|
3. Run the command `csrutil disable`
|
25
25
|
4. Restart your computer"""
|
26
|
-
MSG_NO_PGREP = "pgrep command or psutil python package is necessary"
|
27
26
|
MSG_LAUNCH_FAIL = "Failed to launch Viber"
|
28
27
|
MSG_PERMISSION_ERROR = "Failed to read Viber process memory"
|
29
28
|
|
30
29
|
|
31
|
-
def check_admin_windows() -> bool:
|
32
|
-
username = os.getenv("username")
|
33
|
-
if username is None:
|
34
|
-
return False
|
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:
|
56
|
-
result = False
|
57
|
-
|
58
|
-
while True:
|
59
|
-
pid = find_pid_by_name(name)
|
60
|
-
if pid is not None:
|
61
|
-
os.kill(pid, signal.SIGTERM)
|
62
|
-
result = True
|
63
|
-
else:
|
64
|
-
break
|
65
|
-
|
66
|
-
return result
|
67
|
-
|
68
|
-
|
69
|
-
def find_pid_by_name(name: str) -> Optional[int]:
|
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
|
-
|
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
|
106
|
-
|
107
|
-
for proc in psutil.process_iter(): # type: ignore
|
108
|
-
if name in proc.name().lower():
|
109
|
-
proc.kill()
|
110
|
-
result = True
|
111
|
-
|
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
|
120
|
-
|
121
|
-
|
122
30
|
class GetViberAuth:
|
123
31
|
def __init__(self, cb_ask_str: Callable[..., str] = input):
|
124
32
|
self.cb_ask_str = cb_ask_str
|
@@ -137,113 +45,6 @@ class GetViberAuth:
|
|
137
45
|
|
138
46
|
return find_pid_by_name("viber")
|
139
47
|
|
140
|
-
def get_mem_windows(self, viber_pid: int) -> Tuple[Optional[bytes], str]:
|
141
|
-
from pathlib import WindowsPath
|
142
|
-
|
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)
|
173
|
-
break
|
174
|
-
except PermissionError:
|
175
|
-
pass
|
176
|
-
|
177
|
-
return s, ""
|
178
|
-
|
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()
|
181
|
-
|
182
|
-
s = subprocess.run(
|
183
|
-
[
|
184
|
-
memdump_sh_path,
|
185
|
-
str(viber_pid),
|
186
|
-
],
|
187
|
-
capture_output=True,
|
188
|
-
).stdout
|
189
|
-
|
190
|
-
if len(s) > 1000:
|
191
|
-
pass
|
192
|
-
elif shutil.which("pkexec") and os.getenv("DISPLAY"):
|
193
|
-
s = subprocess.run(
|
194
|
-
[
|
195
|
-
"pkexec",
|
196
|
-
memdump_sh_path,
|
197
|
-
str(viber_pid),
|
198
|
-
],
|
199
|
-
capture_output=True,
|
200
|
-
).stdout
|
201
|
-
else:
|
202
|
-
prompt = "Enter sudo password: "
|
203
|
-
if self.cb_ask_str != input:
|
204
|
-
sudo_password = self.cb_ask_str(
|
205
|
-
prompt, initialvalue="", cli_show_initialvalue=False
|
206
|
-
)
|
207
|
-
else:
|
208
|
-
sudo_password = getpass(prompt)
|
209
|
-
sudo_password_pipe = subprocess.Popen(
|
210
|
-
("echo", sudo_password), stdout=subprocess.PIPE
|
211
|
-
)
|
212
|
-
s = subprocess.run(
|
213
|
-
[
|
214
|
-
"sudo",
|
215
|
-
"-S",
|
216
|
-
memdump_sh_path,
|
217
|
-
str(viber_pid),
|
218
|
-
],
|
219
|
-
capture_output=True,
|
220
|
-
stdin=sudo_password_pipe.stdout,
|
221
|
-
).stdout
|
222
|
-
|
223
|
-
return s, ""
|
224
|
-
|
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
|
-
)
|
239
|
-
|
240
|
-
with open("/tmp/viber.dmp", "rb") as f:
|
241
|
-
s = f.read()
|
242
|
-
|
243
|
-
os.remove("/tmp/viber.dmp")
|
244
|
-
|
245
|
-
return s, ""
|
246
|
-
|
247
48
|
def get_auth_by_pme(
|
248
49
|
self, viber_bin_path: str, relaunch: bool = True
|
249
50
|
) -> Tuple[Optional[str], str]:
|
@@ -315,12 +116,6 @@ class GetViberAuth:
|
|
315
116
|
|
316
117
|
if "enabled" in csrutil_status:
|
317
118
|
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
119
|
|
325
120
|
if relaunch:
|
326
121
|
viber_pid = self.relaunch_viber(viber_bin_path)
|
@@ -329,12 +124,13 @@ class GetViberAuth:
|
|
329
124
|
if viber_pid is None:
|
330
125
|
return None, MSG_LAUNCH_FAIL
|
331
126
|
|
332
|
-
if
|
333
|
-
|
334
|
-
elif platform.system() == "Darwin":
|
335
|
-
s, msg = self.get_mem_darwin(viber_pid)
|
127
|
+
if self.cb_ask_str == input:
|
128
|
+
pw_func = getpass
|
336
129
|
else:
|
337
|
-
|
130
|
+
pw_func = partial(
|
131
|
+
self.cb_ask_str, initialvalue="", cli_show_initialvalue=False
|
132
|
+
)
|
133
|
+
s, msg = get_mem(viber_pid, pw_func)
|
338
134
|
|
339
135
|
if s is None:
|
340
136
|
return None, msg
|
@@ -421,14 +217,14 @@ class GetViberAuth:
|
|
421
217
|
methods.append(self.get_auth_by_dump)
|
422
218
|
if pme_present:
|
423
219
|
methods.append(self.get_auth_by_pme)
|
424
|
-
if
|
220
|
+
if check_admin() is False:
|
425
221
|
methods.reverse()
|
426
222
|
else:
|
427
223
|
if not os.path.isfile("/.dockerenv"):
|
428
224
|
methods.append(self.get_auth_by_dump)
|
429
225
|
if pme_present:
|
430
226
|
methods.append(self.get_auth_by_pme)
|
431
|
-
if
|
227
|
+
if check_admin() is False:
|
432
228
|
methods.reverse()
|
433
229
|
|
434
230
|
for method in methods:
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import platform
|
5
|
+
import shutil
|
6
|
+
import socket
|
7
|
+
import subprocess
|
8
|
+
import time
|
9
|
+
from typing import Any, Dict, Optional, Union, cast
|
10
|
+
|
11
|
+
import requests
|
12
|
+
import websocket
|
13
|
+
|
14
|
+
# References
|
15
|
+
# https://github.com/yeongbin-jo/python-chromedriver-autoinstaller/blob/master/chromedriver_autoinstaller/utils.py
|
16
|
+
# https://chromedevtools.github.io/devtools-protocol/
|
17
|
+
|
18
|
+
|
19
|
+
def get_free_port() -> int:
|
20
|
+
with socket.socket() as sock:
|
21
|
+
sock.bind(("", 0))
|
22
|
+
port = sock.getsockname()[1]
|
23
|
+
return port
|
24
|
+
|
25
|
+
|
26
|
+
class CRD:
|
27
|
+
def __init__(self, chrome_bin: str, port: Optional[int] = None):
|
28
|
+
if port is None:
|
29
|
+
port = get_free_port()
|
30
|
+
self.port = port
|
31
|
+
self.chrome_proc = subprocess.Popen(
|
32
|
+
[
|
33
|
+
chrome_bin,
|
34
|
+
"--no-sandbox",
|
35
|
+
f"--remote-debugging-port={port}",
|
36
|
+
f"--remote-allow-origins=http://localhost:{port}",
|
37
|
+
]
|
38
|
+
)
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def get_chrome_path() -> Optional[str]:
|
42
|
+
chrome_bin: Optional[str]
|
43
|
+
if platform.system() == "Darwin":
|
44
|
+
chrome_bin = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
45
|
+
|
46
|
+
if os.path.isfile(chrome_bin) is False:
|
47
|
+
return None
|
48
|
+
|
49
|
+
return chrome_bin
|
50
|
+
|
51
|
+
elif platform.system() == "Windows":
|
52
|
+
chrome_x64 = f"{os.environ.get('PROGRAMW6432') or os.environ.get('PROGRAMFILES')}\\Google\\Chrome\\Application"
|
53
|
+
chrome_x86 = (
|
54
|
+
f"{os.environ.get('PROGRAMFILES(X86)')}\\Google\\Chrome\\Application"
|
55
|
+
)
|
56
|
+
|
57
|
+
chrome_dir = (
|
58
|
+
chrome_x64
|
59
|
+
if os.path.isdir(chrome_x64)
|
60
|
+
else chrome_x86
|
61
|
+
if os.path.isdir(chrome_x86)
|
62
|
+
else None
|
63
|
+
)
|
64
|
+
|
65
|
+
if chrome_dir is None:
|
66
|
+
return None
|
67
|
+
|
68
|
+
return chrome_dir + "\\chrome.exe"
|
69
|
+
|
70
|
+
else:
|
71
|
+
for executable in (
|
72
|
+
"google-chrome",
|
73
|
+
"google-chrome-stable",
|
74
|
+
"google-chrome-beta",
|
75
|
+
"google-chrome-dev",
|
76
|
+
"chromium-browser",
|
77
|
+
"chromium",
|
78
|
+
):
|
79
|
+
chrome_bin = shutil.which(executable)
|
80
|
+
if chrome_bin is not None:
|
81
|
+
return chrome_bin
|
82
|
+
return None
|
83
|
+
|
84
|
+
def connect(self):
|
85
|
+
self.cmd_id = 1
|
86
|
+
r = None
|
87
|
+
for _ in range(3):
|
88
|
+
try:
|
89
|
+
r = requests.get(f"http://localhost:{self.port}/json")
|
90
|
+
break
|
91
|
+
except requests.exceptions.ConnectionError:
|
92
|
+
time.sleep(1)
|
93
|
+
|
94
|
+
if r is None:
|
95
|
+
raise RuntimeError("Cannot connect to chrome debugging port")
|
96
|
+
|
97
|
+
targets = json.loads(r.text)
|
98
|
+
self.ws = websocket.create_connection(targets[0]["webSocketDebuggerUrl"]) # type: ignore
|
99
|
+
|
100
|
+
def send_cmd(self, command: Dict[Any, Any]) -> Union[str, bytes]:
|
101
|
+
if command.get("id") is None:
|
102
|
+
command["id"] = self.cmd_id
|
103
|
+
for _ in range(3):
|
104
|
+
try:
|
105
|
+
self.ws.send(json.dumps(command))
|
106
|
+
r = self.ws.recv()
|
107
|
+
self.cmd_id += 1
|
108
|
+
return r
|
109
|
+
except BrokenPipeError:
|
110
|
+
self.connect()
|
111
|
+
|
112
|
+
raise RuntimeError("Websocket keep disconnecting")
|
113
|
+
|
114
|
+
def exec_js(self, js: str, context_id: Optional[int] = None):
|
115
|
+
command: Dict[str, Any] = {
|
116
|
+
"id": self.cmd_id,
|
117
|
+
"method": "Runtime.evaluate",
|
118
|
+
"params": {"expression": js},
|
119
|
+
}
|
120
|
+
if context_id is not None:
|
121
|
+
command["params"]["contextId"] = context_id
|
122
|
+
return self.send_cmd(command)
|
123
|
+
|
124
|
+
def get_curr_url(self) -> str:
|
125
|
+
r = self.exec_js("window.location.href")
|
126
|
+
return cast(str, json.loads(r)["result"]["result"]["value"])
|
127
|
+
|
128
|
+
def navigate(self, url: str):
|
129
|
+
command = {"id": self.cmd_id, "method": "Page.navigate", "params": {"url": url}}
|
130
|
+
self.send_cmd(command)
|
131
|
+
|
132
|
+
def runtime_enable(self):
|
133
|
+
command = {
|
134
|
+
"method": "Runtime.enable",
|
135
|
+
}
|
136
|
+
self.send_cmd(command)
|
137
|
+
|
138
|
+
def runtime_disable(self):
|
139
|
+
command = {
|
140
|
+
"method": "Runtime.disable",
|
141
|
+
}
|
142
|
+
self.send_cmd(command)
|
143
|
+
|
144
|
+
def reload(self):
|
145
|
+
command = {
|
146
|
+
"method": "Page.reload",
|
147
|
+
}
|
148
|
+
self.send_cmd(command)
|
149
|
+
|
150
|
+
def close(self):
|
151
|
+
self.ws.close()
|
152
|
+
self.chrome_proc.kill()
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
from sticker_convert.utils.files.json_resources_loader import EMOJI_JSON
|
5
|
+
|
6
|
+
|
7
|
+
def get_emoji_list() -> List[str]:
|
8
|
+
return [i["emoji"] for i in EMOJI_JSON]
|
9
|
+
|
10
|
+
|
11
|
+
EMOJI_LIST = get_emoji_list()
|
12
|
+
|
13
|
+
|
14
|
+
# https://stackoverflow.com/a/43146653
|
15
|
+
def extract_emojis(s: str) -> str:
|
16
|
+
return "".join(set(c for c in s if c in EMOJI_LIST))
|