sticker-convert 2.12.4__py3-none-any.whl → 2.13.1.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/cli.py +3 -0
  2. sticker_convert/converter.py +61 -58
  3. sticker_convert/downloaders/download_band.py +110 -0
  4. sticker_convert/downloaders/download_kakao.py +84 -22
  5. sticker_convert/downloaders/download_line.py +8 -4
  6. sticker_convert/gui.py +6 -3
  7. sticker_convert/gui_components/windows/advanced_compression_window.py +1 -1
  8. sticker_convert/gui_components/windows/discord_get_auth_window.py +3 -3
  9. sticker_convert/gui_components/windows/kakao_get_auth_window.py +25 -4
  10. sticker_convert/gui_components/windows/signal_get_auth_window.py +3 -3
  11. sticker_convert/gui_components/windows/viber_get_auth_window.py +16 -1
  12. sticker_convert/job.py +6 -0
  13. sticker_convert/resources/compression.json +47 -0
  14. sticker_convert/resources/help.json +1 -1
  15. sticker_convert/resources/input.json +10 -0
  16. sticker_convert/resources/memdump_linux.sh +0 -1
  17. sticker_convert/utils/auth/get_discord_auth.py +2 -2
  18. sticker_convert/utils/auth/get_kakao_desktop_auth.py +38 -43
  19. sticker_convert/utils/auth/get_signal_auth.py +2 -2
  20. sticker_convert/utils/auth/get_viber_auth.py +3 -5
  21. sticker_convert/utils/auth/telegram_api.py +3 -1
  22. sticker_convert/utils/auth/telethon_setup.py +21 -8
  23. sticker_convert/utils/chrome_remotedebug.py +27 -29
  24. sticker_convert/utils/chromiums/linux.py +52 -0
  25. sticker_convert/utils/chromiums/osx.py +68 -0
  26. sticker_convert/utils/chromiums/windows.py +45 -0
  27. sticker_convert/utils/media/codec_info.py +1 -1
  28. sticker_convert/utils/process.py +152 -108
  29. sticker_convert/utils/singletons.py +18 -0
  30. sticker_convert/utils/url_detect.py +3 -0
  31. sticker_convert/version.py +1 -1
  32. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/METADATA +37 -29
  33. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/RECORD +37 -32
  34. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/WHEEL +1 -1
  35. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/entry_points.txt +0 -0
  36. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/licenses/LICENSE +0 -0
  37. {sticker_convert-2.12.4.dist-info → sticker_convert-2.13.1.0.dist-info}/top_level.txt +0 -0
@@ -59,10 +59,25 @@ class ViberGetAuthWindow(BaseWindow):
59
59
  justify="left",
60
60
  anchor="w",
61
61
  )
62
+ if platform.system() != "Darwin":
63
+ self.explanation_lbl3 = Label(
64
+ self.frame_info,
65
+ text="Note: This will download ProcDump and read memory of Viber Desktop",
66
+ justify="left",
67
+ anchor="w",
68
+ )
69
+ else:
70
+ self.explanation_lbl3 = Label(
71
+ self.frame_info,
72
+ text="Note: This will read memory of Viber Desktop",
73
+ justify="left",
74
+ anchor="w",
75
+ )
62
76
 
63
77
  self.explanation_lbl0.grid(column=0, row=0, sticky="w", padx=3, pady=3)
64
78
  self.explanation_lbl1.grid(column=0, row=1, sticky="w", padx=3, pady=3)
65
79
  self.explanation_lbl2.grid(column=0, row=2, sticky="w", padx=3, pady=3)
80
+ self.explanation_lbl3.grid(column=0, row=3, sticky="w", padx=3, pady=3)
66
81
 
67
82
  # Start button frame
68
83
  self.launch_btn = Button(
@@ -109,7 +124,7 @@ class ViberGetAuthWindow(BaseWindow):
109
124
 
110
125
  GUIUtils.finalize_window(self)
111
126
 
112
- def cb_get_cred(self):
127
+ def cb_get_cred(self) -> None:
113
128
  Thread(target=self.cb_get_cred_thread, daemon=True).start()
114
129
 
115
130
  def cb_get_cred_thread(self) -> None:
sticker_convert/job.py CHANGED
@@ -12,6 +12,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
12
12
  from urllib.parse import urlparse
13
13
 
14
14
  from sticker_convert.converter import StickerConvert
15
+ from sticker_convert.downloaders.download_band import DownloadBand
15
16
  from sticker_convert.downloaders.download_discord import DownloadDiscord
16
17
  from sticker_convert.downloaders.download_kakao import DownloadKakao
17
18
  from sticker_convert.downloaders.download_line import DownloadLine
@@ -28,6 +29,7 @@ from sticker_convert.utils.callback import CallbackReturn, CbQueueType, ResultsL
28
29
  from sticker_convert.utils.files.json_resources_loader import OUTPUT_JSON
29
30
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
30
31
  from sticker_convert.utils.media.codec_info import CodecInfo
32
+ from sticker_convert.utils.singletons import singletons
31
33
 
32
34
 
33
35
  class Executor:
@@ -136,6 +138,7 @@ class Executor:
136
138
 
137
139
  work_queue.put(None)
138
140
  cb_queue.put("__PROCESS_DONE__")
141
+ singletons.close()
139
142
 
140
143
  def start_workers(self, processes: int = 1) -> None:
141
144
  self.cb_thread_instance = Thread(
@@ -564,6 +567,9 @@ class Job:
564
567
  if self.opt_input.option == "kakao":
565
568
  downloaders.append(DownloadKakao.start)
566
569
 
570
+ if self.opt_input.option == "band":
571
+ downloaders.append(DownloadBand.start)
572
+
567
573
  if self.opt_input.option == "viber":
568
574
  downloaders.append(DownloadViber.start)
569
575
 
@@ -328,6 +328,53 @@
328
328
  "quantize_method": "imagequant",
329
329
  "default_emoji": "😀"
330
330
  },
331
+ "band": {
332
+ "size_max": {
333
+ "img": 0,
334
+ "vid": 0
335
+ },
336
+ "format": {
337
+ "img": ".png",
338
+ "vid": ".png"
339
+ },
340
+ "fps": {
341
+ "min": 1,
342
+ "max": 30,
343
+ "power": -0.5
344
+ },
345
+ "res": {
346
+ "w": {
347
+ "min": 100,
348
+ "max": 370
349
+ },
350
+ "h": {
351
+ "min": 100,
352
+ "max": 320
353
+ },
354
+ "power": 3
355
+ },
356
+ "quality": {
357
+ "min": 10,
358
+ "max": 95,
359
+ "power": 5
360
+ },
361
+ "color": {
362
+ "min": 32,
363
+ "max": 257,
364
+ "power": 3
365
+ },
366
+ "duration": {
367
+ "min": 83,
368
+ "max": 4000
369
+ },
370
+ "padding_percent": 0,
371
+ "bg_color": "",
372
+ "steps": 16,
373
+ "fake_vid": false,
374
+ "scale_filter": "bicubic",
375
+ "quantize_method": "imagequant",
376
+ "default_emoji": "😀"
377
+ },
331
378
  "viber": {
332
379
  "size_max": {
333
380
  "img": 0,
@@ -64,7 +64,7 @@
64
64
  "telethon_setup": "Setup Telethon",
65
65
  "kakao_auth_token": "Set Kakao auth_token. Required for downloading animated stickers from https://e.kakao.com/t/xxxxx",
66
66
  "kakao_get_auth": "Generate Kakao auth_token by simulating login. Kakao username, password, country code and phone number are also required.",
67
- "kakao_get_auth_desktop": "Get Kakao auth_token from Kakao Desktop application.\n(Only working on Windows.)",
67
+ "kakao_get_auth_desktop": "Get Kakao auth_token from Kakao Desktop application.",
68
68
  "kakao_bin_path": "Set Kakao Desktop application path for launching and getting auth_token.\nUseful for portable installation.",
69
69
  "kakao_username": "Set Kakao username, which is email or phone number used for signing up Kakao account\nExample: +447700900142\nRequired for generating Kakao auth_token.",
70
70
  "kakao_password": "Set Kakao password (Password of Kakao account).\nRequired for generating Kakao auth_token.",
@@ -59,6 +59,16 @@
59
59
  "author": true
60
60
  }
61
61
  },
62
+ "band": {
63
+ "full_name": "Download from Naver Band",
64
+ "help": "Download Naver Band stickers from a URL / ID as input",
65
+ "example": "Example: https://www.band.us/sticker/xxxx OR 2535",
66
+ "address_lbls": "URL address / ID",
67
+ "metadata_provides": {
68
+ "title": true,
69
+ "author": false
70
+ }
71
+ },
62
72
  "viber": {
63
73
  "full_name": "Download from Viber",
64
74
  "help": "Download viber stickers from a URL as input",
@@ -7,7 +7,6 @@ PID=$1
7
7
  PID_MAPS=/proc/$PID/maps
8
8
  PID_MEM=/proc/$PID/mem
9
9
 
10
- rm -f /tmp/viber.dmp.$PID
11
10
  grep rw-p $PID_MAPS |
12
11
  while IFS='' read -r line || [[ -n "$line" ]]; do
13
12
  range=`echo $line | awk '{print $1;}'`
@@ -13,7 +13,7 @@ from sticker_convert.utils.process import killall
13
13
 
14
14
 
15
15
  class GetDiscordAuth:
16
- def __init__(self, cb_msg: Callable[..., None] = print):
16
+ def __init__(self, cb_msg: Callable[..., None] = print) -> None:
17
17
  chromedriver_download_dir = CONFIG_DIR / "bin"
18
18
  os.makedirs(chromedriver_download_dir, exist_ok=True)
19
19
 
@@ -72,7 +72,7 @@ class GetDiscordAuth:
72
72
  if chrome_path is not None:
73
73
  using_discord_app = True
74
74
  else:
75
- chrome_path = CRD.get_chrome_path()
75
+ chrome_path = CRD.get_chromium_path()
76
76
  if chrome_path is None:
77
77
  return (
78
78
  None,
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- import importlib.util
3
2
  import os
4
3
  import platform
5
4
  import re
@@ -8,9 +7,9 @@ import time
8
7
  from functools import partial
9
8
  from getpass import getpass
10
9
  from pathlib import Path
11
- from typing import Callable, List, Optional, Tuple, cast
10
+ from typing import Callable, Optional, Tuple, Union, cast
12
11
 
13
- from sticker_convert.utils.process import check_admin, find_pid_by_name, get_mem, killall
12
+ from sticker_convert.utils.process import find_pid_by_name, get_mem, killall
14
13
 
15
14
  MSG_NO_BIN = """Kakao Desktop not detected.
16
15
  Download and install Kakao Desktop,
@@ -28,10 +27,10 @@ MSG_PERMISSION_ERROR = "Failed to read Kakao process memory"
28
27
 
29
28
 
30
29
  class GetKakaoDesktopAuth:
31
- def __init__(self, cb_ask_str: Callable[..., str] = input):
30
+ def __init__(self, cb_ask_str: Callable[..., str] = input) -> None:
32
31
  self.cb_ask_str = cb_ask_str
33
32
 
34
- def launch_kakao(self, kakao_bin_path: str):
33
+ def launch_kakao(self, kakao_bin_path: str) -> None:
35
34
  if platform.system() == "Windows":
36
35
  subprocess.Popen([kakao_bin_path])
37
36
  elif platform.system() == "Darwin":
@@ -97,13 +96,20 @@ class GetKakaoDesktopAuth:
97
96
  self, kakao_bin_path: str, relaunch: bool = True
98
97
  ) -> Tuple[Optional[str], str]:
99
98
  auth_token = None
100
-
101
- if relaunch:
102
- kakao_pid = self.relaunch_kakao(kakao_bin_path)
99
+ kakao_pid: Union[str, int, None]
100
+ if platform.system() == "Windows":
101
+ is_wine = False
102
+ if relaunch:
103
+ kakao_pid = self.relaunch_kakao(kakao_bin_path)
104
+ else:
105
+ kakao_pid = find_pid_by_name("kakaotalk")
106
+ if kakao_pid is None:
107
+ return None, MSG_LAUNCH_FAIL
103
108
  else:
104
- kakao_pid = find_pid_by_name("kakaotalk")
105
- if kakao_pid is None:
106
- return None, MSG_LAUNCH_FAIL
109
+ is_wine = True
110
+ kakao_pid = "KakaoTalk.exe"
111
+ if relaunch and self.relaunch_kakao(kakao_bin_path) is None:
112
+ return None, MSG_LAUNCH_FAIL
107
113
 
108
114
  if self.cb_ask_str == input:
109
115
  pw_func = getpass
@@ -111,10 +117,10 @@ class GetKakaoDesktopAuth:
111
117
  pw_func = partial(
112
118
  self.cb_ask_str, initialvalue="", cli_show_initialvalue=False
113
119
  )
114
- s, msg = get_mem(kakao_pid, pw_func)
120
+ s = get_mem(kakao_pid, pw_func, is_wine)
115
121
 
116
122
  if s is None:
117
- return None, msg
123
+ return None, "Failed to dump memory"
118
124
 
119
125
  auth_token = None
120
126
  for i in re.finditer(b"authorization: ", s):
@@ -140,6 +146,13 @@ class GetKakaoDesktopAuth:
140
146
  return auth_token, msg
141
147
 
142
148
  def get_auth_darwin(self, kakao_bin_path: str) -> Tuple[Optional[str], str]:
149
+ csrutil_status = subprocess.run(
150
+ ["csrutil", "status"], capture_output=True, text=True
151
+ ).stdout
152
+
153
+ if "enabled" in csrutil_status:
154
+ return None, MSG_SIP_ENABLED
155
+
143
156
  killall("kakaotalk")
144
157
 
145
158
  subprocess.run(
@@ -205,9 +218,10 @@ class GetKakaoDesktopAuth:
205
218
  elif platform.system() == "Darwin":
206
219
  kakao_bin_path = "/Applications/KakaoTalk.app"
207
220
  else:
208
- kakao_bin_path = os.path.expanduser(
209
- "~/.wine/drive_c/Program Files (x86)/Kakao/KakaoTalk/KakaoTalk.exe"
210
- )
221
+ wineprefix = os.environ.get("WINEPREFIX")
222
+ if not (wineprefix and Path(wineprefix).exists()):
223
+ wineprefix = os.path.expanduser("~/.wine")
224
+ kakao_bin_path = f"{wineprefix}/drive_c/Program Files (x86)/Kakao/KakaoTalk/KakaoTalk.exe"
211
225
 
212
226
  if Path(kakao_bin_path).exists():
213
227
  return kakao_bin_path
@@ -220,7 +234,7 @@ class GetKakaoDesktopAuth:
220
234
  ) -> Tuple[Optional[str], str]:
221
235
  # get_auth_by_dump()
222
236
  # + Fast
223
- # - Requires admin
237
+ # - Requires downloading procdump, or builtin method that needs admin
224
238
 
225
239
  # get_auth_by_pme()
226
240
  # + No admin (If have permission to read process)
@@ -233,32 +247,13 @@ class GetKakaoDesktopAuth:
233
247
  if not kakao_bin_path:
234
248
  return None, MSG_NO_BIN
235
249
 
236
- if platform.system() != "Darwin":
237
- # If admin, prefer get_auth_by_dump() over get_auth_by_pme(), vice versa
238
- methods: List[Callable[[str, bool], Tuple[Optional[str], str]]] = []
239
- relaunch = True
240
- kakao_auth = None
241
- msg = ""
242
-
243
- pme_present = importlib.util.find_spec("PyMemoryEditor") is not None
244
- methods.append(self.get_auth_by_dump)
245
- if pme_present:
246
- methods.append(self.get_auth_by_pme)
247
- if check_admin() is False:
248
- methods.reverse()
249
-
250
- for method in methods:
251
- kakao_auth, msg = method(kakao_bin_path, relaunch)
252
- relaunch = False
253
- if kakao_auth is not None:
254
- break
255
- else:
256
- csrutil_status = subprocess.run(
257
- ["csrutil", "status"], capture_output=True, text=True
258
- ).stdout
259
-
260
- if "enabled" in csrutil_status:
261
- return None, MSG_SIP_ENABLED
250
+ if platform.system() == "Windows":
251
+ kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
252
+ if kakao_auth is None:
253
+ kakao_auth, msg = self.get_auth_by_pme(kakao_bin_path, False)
254
+ elif platform.system() == "Darwin":
262
255
  kakao_auth, msg = self.get_auth_darwin(kakao_bin_path)
256
+ else:
257
+ kakao_auth, msg = self.get_auth_by_dump(kakao_bin_path)
263
258
 
264
259
  return kakao_auth, msg
@@ -17,7 +17,7 @@ class GetSignalAuth:
17
17
  self,
18
18
  cb_msg: Callable[..., None] = print,
19
19
  cb_ask_str: Callable[..., str] = input,
20
- ):
20
+ ) -> None:
21
21
  chromedriver_download_dir = CONFIG_DIR / "bin"
22
22
  os.makedirs(chromedriver_download_dir, exist_ok=True)
23
23
 
@@ -26,7 +26,7 @@ class GetSignalAuth:
26
26
  self.cb_ask_str = cb_ask_str
27
27
  self.cb_msg = cb_msg
28
28
 
29
- def download_signal_desktop(self):
29
+ def download_signal_desktop(self) -> None:
30
30
  download_url = "https://signal.org/en/download/"
31
31
 
32
32
  webbrowser.open(download_url)
@@ -28,7 +28,7 @@ MSG_PERMISSION_ERROR = "Failed to read Viber process memory"
28
28
 
29
29
 
30
30
  class GetViberAuth:
31
- def __init__(self, cb_ask_str: Callable[..., str] = input):
31
+ def __init__(self, cb_ask_str: Callable[..., str] = input) -> None:
32
32
  self.cb_ask_str = cb_ask_str
33
33
 
34
34
  def relaunch_viber(self, viber_bin_path: str) -> Optional[int]:
@@ -130,10 +130,10 @@ class GetViberAuth:
130
130
  pw_func = partial(
131
131
  self.cb_ask_str, initialvalue="", cli_show_initialvalue=False
132
132
  )
133
- s, msg = get_mem(viber_pid, pw_func)
133
+ s = get_mem(viber_pid, pw_func)
134
134
 
135
135
  if s is None:
136
- return None, msg
136
+ return None, "Failed to dump memory"
137
137
 
138
138
  member_id_addr = s.find(b"X-Viber-Auth-Mid: ")
139
139
  m_token_addr = s.find(b"X-Viber-Auth-Token: ")
@@ -217,8 +217,6 @@ class GetViberAuth:
217
217
  methods.append(self.get_auth_by_dump)
218
218
  if pme_present:
219
219
  methods.append(self.get_auth_by_pme)
220
- if check_admin() is False:
221
- methods.reverse()
222
220
  else:
223
221
  if not os.path.isfile("/.dockerenv"):
224
222
  methods.append(self.get_auth_by_dump)
@@ -342,10 +342,12 @@ class TelethonAPI(TelegramAPI):
342
342
  self.cb = cb
343
343
  self.cb_return = cb_return
344
344
 
345
- success, self.client, _, _ = await TelethonSetup(
345
+ success, client, _, _ = await TelethonSetup(
346
346
  self.opt_cred, self.cb_ask_str
347
347
  ).start_async()
348
348
 
349
+ if success is True and client is not None:
350
+ self.client = client
349
351
  return success
350
352
 
351
353
  async def exit(self) -> None:
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- from typing import Callable, Tuple
2
+ from typing import Callable, Optional, Tuple
3
3
 
4
4
  import anyio
5
5
  from telethon import TelegramClient # type: ignore
@@ -22,7 +22,9 @@ Continue when done"""
22
22
 
23
23
 
24
24
  class TelethonSetup:
25
- def __init__(self, opt_cred: CredOption, cb_ask_str: Callable[..., str] = input):
25
+ def __init__(
26
+ self, opt_cred: CredOption, cb_ask_str: Callable[..., str] = input
27
+ ) -> None:
26
28
  self.cb_ask_str = cb_ask_str
27
29
  self.opt_cred = opt_cred
28
30
 
@@ -56,28 +58,39 @@ class TelethonSetup:
56
58
  def guide(self) -> None:
57
59
  self.cb_ask_str(GUIDE_MSG)
58
60
 
59
- def get_api_info(self) -> None:
61
+ def get_api_info(self) -> bool:
60
62
  api_id_ask = "Enter api_id: "
61
63
  wrong_hint = ""
64
+
62
65
  while True:
63
66
  telethon_api_id = self.cb_ask_str(wrong_hint + api_id_ask)
64
- if telethon_api_id.isnumeric():
67
+ if telethon_api_id == "":
68
+ return False
69
+ elif telethon_api_id.isnumeric():
65
70
  self.opt_cred.telethon_api_id = int(telethon_api_id)
66
71
  break
67
72
  else:
68
73
  wrong_hint = "Error: api_id should be numeric\n"
74
+
69
75
  self.opt_cred.telethon_api_hash = self.cb_ask_str("Enter api_hash: ")
76
+ if self.opt_cred.telethon_api_hash == "":
77
+ return False
78
+ return True
70
79
 
71
80
  def signin(self) -> Tuple[bool, TelegramClient, int, str]:
72
81
  return anyio.run(self.signin_async)
73
82
 
74
- def start(self) -> Tuple[bool, TelegramClient, int, str]:
83
+ def start(self) -> Tuple[bool, Optional[TelegramClient], int, str]:
84
+ cred_valid = False
75
85
  if self.opt_cred.telethon_api_id == 0 or self.opt_cred.telethon_api_hash == "":
76
86
  self.guide()
77
- self.get_api_info()
78
- return self.signin()
87
+ cred_valid = self.get_api_info()
88
+ if cred_valid:
89
+ return self.signin()
90
+ else:
91
+ return False, None, 0, ""
79
92
 
80
- async def start_async(self) -> Tuple[bool, TelegramClient, int, str]:
93
+ async def start_async(self) -> Tuple[bool, Optional[TelegramClient], int, str]:
81
94
  if self.opt_cred.telethon_api_id == 0 or self.opt_cred.telethon_api_hash == "":
82
95
  self.guide()
83
96
  self.get_api_info()
@@ -5,12 +5,12 @@ import json
5
5
  import os
6
6
  import platform
7
7
  import shutil
8
+ import signal
8
9
  import socket
9
10
  import subprocess
10
11
  import time
11
- from typing import Any, Dict, List, Optional, Tuple, Union, cast
12
+ from typing import Any, Dict, List, Optional, Union, cast
12
13
 
13
- import browsers # type: ignore
14
14
  import requests
15
15
  import websocket
16
16
  from PIL import Image
@@ -50,7 +50,7 @@ class CRD:
50
50
  chrome_bin: str,
51
51
  port: Optional[int] = None,
52
52
  args: Optional[List[str]] = None,
53
- ):
53
+ ) -> None:
54
54
  if port is None:
55
55
  port = get_free_port()
56
56
  self.port = port
@@ -83,22 +83,16 @@ class CRD:
83
83
  self.chrome_proc = subprocess.Popen(launch_cmd)
84
84
 
85
85
  @staticmethod
86
- def get_chrome_path() -> Optional[str]:
87
- bs: List[Tuple[int, str]] = []
88
- for b in browsers.browsers():
89
- browser_type = b["browser_type"]
90
- path = b["path"]
91
- try:
92
- rank = BROWSER_PREF.index(browser_type)
93
- except ValueError:
94
- continue
95
- bs.append((rank, path))
96
- if len(bs) == 0:
97
- return None
98
- bs = sorted(bs, key=lambda x: x[0])
99
- return bs[0][1]
100
-
101
- def connect(self, target_id: int = 0):
86
+ def get_chromium_path() -> Optional[str]:
87
+ if platform.system() == "Windows":
88
+ from sticker_convert.utils.chromiums.windows import get_chromium_path
89
+ elif platform.system() == "Darwin":
90
+ from sticker_convert.utils.chromiums.osx import get_chromium_path
91
+ else:
92
+ from sticker_convert.utils.chromiums.linux import get_chromium_path
93
+ return get_chromium_path()
94
+
95
+ def connect(self, target_id: int = 0) -> None:
102
96
  self.cmd_id = 1
103
97
  r = None
104
98
  targets: List[Any] = []
@@ -137,7 +131,7 @@ class CRD:
137
131
 
138
132
  raise RuntimeError("Websocket keep disconnecting")
139
133
 
140
- def exec_js(self, js: str, context_id: Optional[int] = None):
134
+ def exec_js(self, js: str, context_id: Optional[int] = None) -> Union[str, bytes]:
141
135
  command: Dict[str, Any] = {
142
136
  "id": self.cmd_id,
143
137
  "method": "Runtime.evaluate",
@@ -155,11 +149,11 @@ class CRD:
155
149
  }
156
150
  return self.send_cmd(command)
157
151
 
158
- def screenshot(self, clip: Optional[Dict[str, int]] = None):
152
+ def screenshot(self, clip: Optional[Dict[str, int]] = None) -> Image.Image:
159
153
  command: Dict[str, Any] = {
160
154
  "id": self.cmd_id,
161
155
  "method": "Page.captureScreenshot",
162
- "params": {},
156
+ "params": {"captureBeyondViewport": True, "optimizeForSpeed": True},
163
157
  }
164
158
  if clip:
165
159
  command["params"]["clip"] = clip
@@ -174,11 +168,11 @@ class CRD:
174
168
  str, json.loads(r).get("result", {}).get("result", {}).get("value", "")
175
169
  )
176
170
 
177
- def navigate(self, url: str):
171
+ def navigate(self, url: str) -> None:
178
172
  command = {"id": self.cmd_id, "method": "Page.navigate", "params": {"url": url}}
179
173
  self.send_cmd(command)
180
174
 
181
- def open_html_str(self, html: str):
175
+ def open_html_str(self, html: str) -> None:
182
176
  command: Dict[str, Any] = {
183
177
  "id": self.cmd_id,
184
178
  "method": "Page.navigate",
@@ -198,24 +192,28 @@ class CRD:
198
192
  }
199
193
  self.send_cmd(command)
200
194
 
201
- def runtime_enable(self):
195
+ def runtime_enable(self) -> None:
202
196
  command = {
203
197
  "method": "Runtime.enable",
204
198
  }
205
199
  self.send_cmd(command)
206
200
 
207
- def runtime_disable(self):
201
+ def runtime_disable(self) -> None:
208
202
  command = {
209
203
  "method": "Runtime.disable",
210
204
  }
211
205
  self.send_cmd(command)
212
206
 
213
- def reload(self):
207
+ def reload(self) -> None:
214
208
  command = {
215
209
  "method": "Page.reload",
216
210
  }
217
211
  self.send_cmd(command)
218
212
 
219
- def close(self):
213
+ def close(self) -> None:
214
+ command = {
215
+ "method": "Browser.close",
216
+ }
217
+ self.send_cmd(command)
220
218
  self.ws.close()
221
- self.chrome_proc.kill()
219
+ os.kill(self.chrome_proc.pid, signal.SIGTERM)
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ import configparser
3
+ import os
4
+ import re
5
+ from typing import Optional
6
+
7
+ # Adopted from https://github.com/roniemartinez/browsers/blob/master/browsers/linux.py
8
+
9
+ LINUX_DESKTOP_ENTRY_LIST = (
10
+ ("chrome", ("google-chrome",)),
11
+ ("chromium", ("chromium", "chromium_chromium")),
12
+ ("brave", ("brave-browser", "brave_brave")),
13
+ ("brave-beta", ("brave-browser-beta",)),
14
+ ("brave-nightly", ("brave-browser-nightly",)),
15
+ ("msedge", ("microsoft-edge",)),
16
+ ("opera", ("opera_opera",)),
17
+ ("opera-beta", ("opera-beta_opera-beta",)),
18
+ ("opera-developer", ("opera-developer_opera-developer",)),
19
+ ("vivaldi", ("vivaldi_vivaldi-stable",)),
20
+ )
21
+
22
+ # $XDG_DATA_HOME and $XDG_DATA_DIRS are not always set
23
+ XDG_DATA_LOCATIONS = (
24
+ "~/.local/share/applications",
25
+ "/usr/share/applications",
26
+ "/var/lib/snapd/desktop/applications",
27
+ )
28
+
29
+ VERSION_PATTERN = re.compile(
30
+ r"\b(\S+\.\S+)\b"
31
+ ) # simple pattern assuming all version strings have a dot on them
32
+
33
+
34
+ def get_chromium_path() -> Optional[str]:
35
+ for _, desktop_entries in LINUX_DESKTOP_ENTRY_LIST:
36
+ for application_dir in XDG_DATA_LOCATIONS:
37
+ for desktop_entry in desktop_entries:
38
+ path = os.path.join(application_dir, f"{desktop_entry}.desktop")
39
+
40
+ if not os.path.isfile(path):
41
+ continue
42
+
43
+ config = configparser.ConfigParser(interpolation=None)
44
+ config.read(path, encoding="utf-8")
45
+ executable_path = config.get("Desktop Entry", "Exec")
46
+
47
+ if executable_path.lower().endswith(" %u"):
48
+ executable_path = executable_path[:-3].strip()
49
+
50
+ return executable_path
51
+
52
+ return None