sticker-convert 2.9.4__py3-none-any.whl → 2.10.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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 +21 -1
- 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 +154 -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.1.dist-info}/METADATA +41 -43
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.1.dist-info}/RECORD +35 -29
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.1.dist-info}/LICENSE +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.1.dist-info}/WHEEL +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.1.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.9.4.dist-info → sticker_convert-2.10.1.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,154 @@
|
|
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(
|
127
|
+
str, json.loads(r).get("result", {}).get("result", {}).get("value", "")
|
128
|
+
)
|
129
|
+
|
130
|
+
def navigate(self, url: str):
|
131
|
+
command = {"id": self.cmd_id, "method": "Page.navigate", "params": {"url": url}}
|
132
|
+
self.send_cmd(command)
|
133
|
+
|
134
|
+
def runtime_enable(self):
|
135
|
+
command = {
|
136
|
+
"method": "Runtime.enable",
|
137
|
+
}
|
138
|
+
self.send_cmd(command)
|
139
|
+
|
140
|
+
def runtime_disable(self):
|
141
|
+
command = {
|
142
|
+
"method": "Runtime.disable",
|
143
|
+
}
|
144
|
+
self.send_cmd(command)
|
145
|
+
|
146
|
+
def reload(self):
|
147
|
+
command = {
|
148
|
+
"method": "Page.reload",
|
149
|
+
}
|
150
|
+
self.send_cmd(command)
|
151
|
+
|
152
|
+
def close(self):
|
153
|
+
self.ws.close()
|
154
|
+
self.chrome_proc.kill()
|