sticker-convert 2.10.8__py3-none-any.whl → 2.11.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 (32) hide show
  1. sticker_convert/cli.py +39 -1
  2. sticker_convert/converter.py +10 -6
  3. sticker_convert/downloaders/download_base.py +37 -17
  4. sticker_convert/downloaders/download_discord.py +6 -6
  5. sticker_convert/downloaders/download_kakao.py +31 -13
  6. sticker_convert/downloaders/download_line.py +6 -6
  7. sticker_convert/downloaders/download_signal.py +10 -8
  8. sticker_convert/downloaders/download_telegram.py +22 -96
  9. sticker_convert/downloaders/download_viber.py +8 -6
  10. sticker_convert/gui.py +12 -0
  11. sticker_convert/gui_components/frames/cred_frame.py +38 -13
  12. sticker_convert/job.py +84 -63
  13. sticker_convert/job_option.py +6 -0
  14. sticker_convert/resources/compression.json +2 -2
  15. sticker_convert/resources/help.json +3 -2
  16. sticker_convert/resources/input.json +10 -0
  17. sticker_convert/resources/output.json +16 -0
  18. sticker_convert/uploaders/compress_wastickers.py +8 -6
  19. sticker_convert/uploaders/upload_signal.py +10 -6
  20. sticker_convert/uploaders/upload_telegram.py +178 -231
  21. sticker_convert/uploaders/upload_viber.py +12 -8
  22. sticker_convert/uploaders/xcode_imessage.py +8 -6
  23. sticker_convert/utils/auth/telegram_api.py +668 -0
  24. sticker_convert/utils/auth/telethon_setup.py +79 -0
  25. sticker_convert/utils/url_detect.py +1 -1
  26. sticker_convert/version.py +1 -1
  27. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/METADATA +54 -36
  28. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/RECORD +32 -30
  29. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/LICENSE +0 -0
  30. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/WHEEL +0 -0
  31. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/entry_points.txt +0 -0
  32. {sticker_convert-2.10.8.dist-info → sticker_convert-2.11.0.dist-info}/top_level.txt +0 -0
sticker_convert/cli.py CHANGED
@@ -20,6 +20,7 @@ from sticker_convert.utils.auth.get_kakao_desktop_auth import GetKakaoDesktopAut
20
20
  from sticker_convert.utils.auth.get_line_auth import GetLineAuth
21
21
  from sticker_convert.utils.auth.get_signal_auth import GetSignalAuth
22
22
  from sticker_convert.utils.auth.get_viber_auth import GetViberAuth
23
+ from sticker_convert.utils.auth.telethon_setup import TelethonSetup
23
24
  from sticker_convert.utils.callback import Callback
24
25
  from sticker_convert.utils.files.json_manager import JsonManager
25
26
  from sticker_convert.utils.url_detect import UrlDetect
@@ -168,10 +169,12 @@ class CLI:
168
169
  parser_cred = parser.add_argument_group("Credentials options")
169
170
  flags_cred_bool = (
170
171
  "signal_get_auth",
172
+ "telethon_setup",
171
173
  "kakao_get_auth",
172
174
  "kakao_get_auth_desktop",
173
175
  "line_get_auth",
174
176
  "discord_get_auth",
177
+ "save_cred",
175
178
  )
176
179
  for k, v in self.help["cred"].items():
177
180
  keyword_args = {}
@@ -226,6 +229,7 @@ class CLI:
226
229
  "signal": args.download_signal,
227
230
  "line": args.download_line,
228
231
  "telegram": args.download_telegram,
232
+ "telegram_telethon": args.download_telegram_telethon,
229
233
  "kakao": args.download_kakao,
230
234
  "viber": args.download_viber,
231
235
  "discord": args.download_discord,
@@ -266,6 +270,14 @@ class CLI:
266
270
  export_option = "signal"
267
271
  elif args.export_telegram:
268
272
  export_option = "telegram"
273
+ elif args.export_telegram_emoji:
274
+ export_option = "telegram_emoji"
275
+ elif args.export_telegram_telethon:
276
+ export_option = "telegram_telethon"
277
+ elif args.export_telegram_emoji_telethon:
278
+ export_option = "telegram_emoji_telethon"
279
+ elif args.export_viber:
280
+ export_option = "viber"
269
281
  elif args.export_imessage:
270
282
  export_option = "imessage"
271
283
  else:
@@ -291,6 +303,10 @@ class CLI:
291
303
  args.export_whatsapp,
292
304
  args.export_signal,
293
305
  args.export_telegram,
306
+ args.export_telegram_emoji,
307
+ args.export_telegram_telethon,
308
+ args.export_telegram_emoji_telethon,
309
+ args.export_viber,
294
310
  args.export_imessage,
295
311
  )
296
312
  )
@@ -302,8 +318,12 @@ class CLI:
302
318
  preset = "whatsapp"
303
319
  elif args.export_signal:
304
320
  preset = "signal"
305
- elif args.export_telegram:
321
+ elif args.export_telegram or args.export_telegram_telethon:
306
322
  preset = "telegram"
323
+ elif args.export_telegram_emoji or args.export_telegram_emoji_telethon:
324
+ preset = "telegram_emoji"
325
+ elif args.export_viber:
326
+ preset = "viber"
307
327
  elif args.export_imessage:
308
328
  preset = "imessage_small"
309
329
  elif args.preset == "auto":
@@ -316,6 +336,12 @@ class CLI:
316
336
  self.cb.msg(
317
337
  "Auto compression option set to no_compress (Reason: Export to local directory only)"
318
338
  )
339
+ elif "telegram_emoji" in output_option:
340
+ preset = "telegram_emoji"
341
+ self.cb.msg(f"Auto compression option set to {preset}")
342
+ elif "telegram" in output_option:
343
+ preset = "telegram"
344
+ self.cb.msg(f"Auto compression option set to {preset}")
319
345
  elif output_option == "imessage":
320
346
  preset = "imessage_small"
321
347
  self.cb.msg(f"Auto compression option set to {preset}")
@@ -445,6 +471,8 @@ class CLI:
445
471
  telegram_userid=args.telegram_userid
446
472
  if args.telegram_userid
447
473
  else creds.get("telegram", {}).get("userid"),
474
+ telethon_api_id=creds.get("telethon", {}).get("api_id"),
475
+ telethon_api_hash=creds.get("telethon", {}).get("api_hash"),
448
476
  kakao_auth_token=args.kakao_auth_token
449
477
  if args.kakao_auth_token
450
478
  else creds.get("kakao", {}).get("auth_token"),
@@ -511,6 +539,16 @@ class CLI:
511
539
 
512
540
  self.cb.msg("Failed to get uuid and password")
513
541
 
542
+ if args.telethon_setup:
543
+ telethon_setup = TelethonSetup(opt_cred, self.cb.ask_str)
544
+ success, _, telethon_api_id, telethon_api_hash = telethon_setup.start()
545
+
546
+ if success:
547
+ opt_cred.telethon_api_id = telethon_api_id
548
+ opt_cred.telethon_api_hash = telethon_api_hash
549
+
550
+ self.cb.msg("Telethon setup successful")
551
+
514
552
  if args.line_get_auth:
515
553
  get_line_auth = GetLineAuth()
516
554
 
@@ -434,7 +434,7 @@ class StickerConvert:
434
434
  # Pillow is not reliable for getting webp frame durations
435
435
  durations: Optional[List[int]]
436
436
  if im.format == "WEBP":
437
- _, _, _, durations = CodecInfo._get_file_fps_frames_duration_webp(
437
+ _, _, _, durations = CodecInfo._get_file_fps_frames_duration_webp( # type: ignore
438
438
  self.in_f
439
439
  )
440
440
  else:
@@ -702,9 +702,9 @@ class StickerConvert:
702
702
  self._frames_export_apng()
703
703
  else:
704
704
  self._frames_export_png()
705
- elif self.out_f.suffix == ".gif":
705
+ elif self.out_f.suffix in (".gif", ".webp"):
706
706
  self._frames_export_pil_anim()
707
- elif self.out_f.suffix in (".webm", ".mp4", ".mkv", ".webp") or is_animated:
707
+ elif self.out_f.suffix in (".webm", ".mp4", ".mkv") or is_animated:
708
708
  self._frames_export_pyav()
709
709
  else:
710
710
  self._frames_export_pil()
@@ -764,7 +764,7 @@ class StickerConvert:
764
764
  format=self.out_f.suffix.replace(".", ""),
765
765
  options=options_container,
766
766
  ) as output:
767
- out_stream = output.add_stream(codec, rate=self.fps, options=options_stream)
767
+ out_stream = output.add_stream(codec, rate=self.fps, options=options_stream) # type: ignore
768
768
  out_stream = cast(VideoStream, out_stream)
769
769
  assert isinstance(self.res_w, int) and isinstance(self.res_h, int)
770
770
  out_stream.width = self.res_w
@@ -811,8 +811,12 @@ class StickerConvert:
811
811
  elif self.out_f.suffix == ".webp":
812
812
  im_out = [Image.fromarray(i) for i in self.frames_processed] # type: ignore
813
813
  extra_kwargs["format"] = "WebP"
814
- extra_kwargs["minimize_size"] = True
815
- extra_kwargs["method"] = 6
814
+ extra_kwargs["allow_mixed"] = True
815
+ if self.quality:
816
+ if self.quality < 20:
817
+ extra_kwargs["minimize_size"] = True
818
+ extra_kwargs["method"] = 4 + int(2 * (100 - self.quality) / 100)
819
+ extra_kwargs["alpha_quality"] = self.quality
816
820
  else:
817
821
  raise RuntimeError(f"Invalid format {self.out_f.suffix}")
818
822
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
 
4
4
  from functools import partial
5
5
  from pathlib import Path
6
- from typing import Any, List, Optional, Tuple
6
+ from typing import Any, Dict, List, Optional, Tuple
7
7
 
8
8
  import anyio
9
9
  import httpx
@@ -34,18 +34,26 @@ class DownloadBase:
34
34
  retries: int = 3,
35
35
  headers: Optional[dict[Any, Any]] = None,
36
36
  **kwargs: Any,
37
- ) -> None:
37
+ ) -> Dict[str, bool]:
38
+ results: Dict[str, bool] = {}
38
39
  anyio.run(
39
40
  partial(
40
- self.download_multiple_files_async, targets, retries, headers, **kwargs
41
+ self.download_multiple_files_async,
42
+ targets,
43
+ retries,
44
+ headers,
45
+ results,
46
+ **kwargs,
41
47
  )
42
48
  )
49
+ return results
43
50
 
44
51
  async def download_multiple_files_async(
45
52
  self,
46
53
  targets: List[Tuple[str, Path]],
47
54
  retries: int = 3,
48
55
  headers: Optional[dict[Any, Any]] = None,
56
+ results: Optional[dict[str, bool]] = None,
49
57
  **kwargs: Any,
50
58
  ) -> None:
51
59
  # targets format: [(url1, dest2), (url2, dest2), ...]
@@ -53,44 +61,56 @@ class DownloadBase:
53
61
  ("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
54
62
  )
55
63
 
64
+ semaphore = anyio.Semaphore(4)
65
+
56
66
  async with httpx.AsyncClient() as client:
57
67
  async with anyio.create_task_group() as tg:
58
68
  for url, dest in targets:
59
69
  tg.start_soon(
60
70
  self.download_file_async,
71
+ semaphore,
61
72
  client,
62
73
  url,
63
74
  dest,
64
75
  retries,
65
76
  headers,
77
+ results,
66
78
  **kwargs,
67
79
  )
68
80
 
69
81
  async def download_file_async(
70
82
  self,
83
+ semaphore: anyio.Semaphore,
71
84
  client: httpx.AsyncClient,
72
85
  url: str,
73
86
  dest: Path,
74
87
  retries: int = 3,
75
88
  headers: Optional[dict[Any, Any]] = None,
89
+ results: Optional[dict[str, bool]] = None,
76
90
  **kwargs: Any,
77
91
  ) -> None:
78
- self.cb.put(f"Downloading {url}")
79
- for retry in range(retries):
80
- response = await client.get(
81
- url, follow_redirects=True, headers=headers, **kwargs
82
- )
83
-
84
- if response.is_success:
85
- async with await anyio.open_file(dest, "wb+") as f:
86
- await f.write(response.content)
87
- self.cb.put(f"Downloaded {url}")
88
- else:
89
- self.cb.put(
90
- f"Error {response.status_code}: {url} (tried {retry+1}/{retries} times)"
92
+ async with semaphore:
93
+ self.cb.put(f"Downloading {url}")
94
+ success = False
95
+ for retry in range(retries):
96
+ response = await client.get(
97
+ url, follow_redirects=True, headers=headers, **kwargs
91
98
  )
99
+ success = response.is_success
100
+
101
+ if success:
102
+ async with await anyio.open_file(dest, "wb+") as f:
103
+ await f.write(response.content)
104
+ self.cb.put(f"Downloaded {url}")
105
+ else:
106
+ self.cb.put(
107
+ f"Error {response.status_code}: {url} (tried {retry+1}/{retries} times)"
108
+ )
109
+
110
+ if results is not None:
111
+ results[url] = success
92
112
 
93
- self.cb.put("update_bar")
113
+ self.cb.put("update_bar")
94
114
 
95
115
  def download_file(
96
116
  self,
@@ -21,10 +21,10 @@ class DownloadDiscord(DownloadBase):
21
21
  # def __init__(self, *args: Any, **kwargs: Any) -> None:
22
22
  # super().__init__(*args, **kwargs)
23
23
 
24
- def download_stickers_discord(self) -> bool:
24
+ def download_stickers_discord(self) -> Tuple[int, int]:
25
25
  if self.opt_cred is None or self.opt_cred.discord_token == "":
26
26
  self.cb.put("Error: Downloading from Discord requires token")
27
- return False
27
+ return 0, 0
28
28
 
29
29
  gid: Optional[str] = None
30
30
  if self.url.isnumeric():
@@ -36,7 +36,7 @@ class DownloadDiscord(DownloadBase):
36
36
 
37
37
  if gid is None or gid.isnumeric() is False:
38
38
  self.cb.put("Error: Invalid url")
39
- return False
39
+ return 0, 0
40
40
 
41
41
  headers = {
42
42
  "Authorization": self.opt_cred.discord_token,
@@ -68,7 +68,7 @@ class DownloadDiscord(DownloadBase):
68
68
  f_path = Path(self.out_dir, f_name)
69
69
  targets.append((sticker_url, f_path))
70
70
 
71
- self.download_multiple_files(targets)
71
+ results = self.download_multiple_files(targets)
72
72
 
73
73
  server_name = r_json["name"]
74
74
  MetadataHandler.set_metadata(
@@ -78,7 +78,7 @@ class DownloadDiscord(DownloadBase):
78
78
  emoji_dict=emoji_dict if self.input_option == "discord" else None,
79
79
  )
80
80
 
81
- return True
81
+ return sum(results.values()), len(targets)
82
82
 
83
83
  @staticmethod
84
84
  def start(
@@ -86,6 +86,6 @@ class DownloadDiscord(DownloadBase):
86
86
  opt_cred: Optional[CredOption],
87
87
  cb: CallbackProtocol,
88
88
  cb_return: CallbackReturn,
89
- ) -> bool:
89
+ ) -> Tuple[int, int]:
90
90
  downloader = DownloadDiscord(opt_input, opt_cred, cb, cb_return)
91
91
  return downloader.download_stickers_discord()
@@ -34,6 +34,11 @@ class daumtools {
34
34
  return dataDict['urlScheme'];
35
35
  }
36
36
  }
37
+ class document {
38
+ static querySelectorAll(selectors) {
39
+ return [];
40
+ }
41
+ }
37
42
  """
38
43
 
39
44
 
@@ -133,9 +138,21 @@ class DownloadKakao(DownloadBase):
133
138
  kakao_url = cast(str, ctx.eval(js))
134
139
  item_code = urlparse(kakao_url).path.split("/")[2]
135
140
 
141
+ # Share link redirect to preview link if use desktop headers
142
+ # This allows us to find pack author
143
+ headers_desktop = {"User-Agent": "Chrome"}
144
+
145
+ response = requests.get(url, headers=headers_desktop, allow_redirects=True)
146
+ response.url
147
+
148
+ pack_title_url = urlparse(response.url).path.split("/")[-1]
149
+ pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(pack_title_url)
150
+ if pack_info_unauthed:
151
+ self.author = pack_info_unauthed["result"]["artist"]
152
+
136
153
  return pack_title, item_code
137
154
 
138
- def download_stickers_kakao(self) -> bool:
155
+ def download_stickers_kakao(self) -> Tuple[int, int]:
139
156
  self.auth_token = None
140
157
  if self.opt_cred:
141
158
  self.auth_token = self.opt_cred.kakao_auth_token
@@ -146,7 +163,7 @@ class DownloadKakao(DownloadBase):
146
163
  if item_code:
147
164
  return self.download_animated(item_code)
148
165
  self.cb.put("Download failed: Cannot download metadata for sticker pack")
149
- return False
166
+ return 0, 0
150
167
 
151
168
  if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
152
169
  item_code = self.url.replace("kakaotalk://store/emoticon/", "")
@@ -177,7 +194,7 @@ class DownloadKakao(DownloadBase):
177
194
  self.cb.put(
178
195
  "Download failed: Cannot download metadata for sticker pack"
179
196
  )
180
- return False
197
+ return 0, 0
181
198
 
182
199
  self.author = self.pack_info_unauthed["result"]["artist"]
183
200
  title_ko = self.pack_info_unauthed["result"]["title"]
@@ -197,14 +214,15 @@ class DownloadKakao(DownloadBase):
197
214
  response = False
198
215
 
199
216
  if response is False:
200
- return False
217
+ return 0, 0
201
218
 
202
219
  return self.download_static(thumbnail_urls)
203
220
 
204
221
  self.cb.put("Download failed: Unrecognized URL")
205
- return False
222
+ return 0, 0
206
223
 
207
- def download_static(self, thumbnail_urls: str) -> bool:
224
+ def download_static(self, thumbnail_urls: str) -> Tuple[int, int]:
225
+ headers = {"User-Agent": "Android"}
208
226
  MetadataHandler.set_metadata(
209
227
  self.out_dir, title=self.pack_title, author=self.author
210
228
  )
@@ -215,11 +233,11 @@ class DownloadKakao(DownloadBase):
215
233
  dest = Path(self.out_dir, str(num).zfill(3) + ".png")
216
234
  targets.append((url, dest))
217
235
 
218
- self.download_multiple_files(targets)
236
+ results = self.download_multiple_files(targets, headers=headers)
219
237
 
220
- return True
238
+ return sum(results.values()), len(targets)
221
239
 
222
- def download_animated(self, item_code: str) -> bool:
240
+ def download_animated(self, item_code: str) -> Tuple[int, int]:
223
241
  MetadataHandler.set_metadata(
224
242
  self.out_dir, title=self.pack_title, author=self.author
225
243
  )
@@ -272,7 +290,7 @@ class DownloadKakao(DownloadBase):
272
290
  break
273
291
  if play_ext == "":
274
292
  self.cb.put(f"Failed to determine extension of {item_code}")
275
- return False
293
+ return 0, 0
276
294
  else:
277
295
  play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
278
296
  else:
@@ -308,7 +326,7 @@ class DownloadKakao(DownloadBase):
308
326
  sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
309
327
  targets.append((sound_url, sound_dl_path))
310
328
 
311
- self.download_multiple_files(targets, headers=headers)
329
+ results = self.download_multiple_files(targets, headers=headers)
312
330
 
313
331
  for target in targets:
314
332
  f_path = target[1]
@@ -326,7 +344,7 @@ class DownloadKakao(DownloadBase):
326
344
 
327
345
  self.cb.put(f"Finished getting {item_code}")
328
346
 
329
- return True
347
+ return sum(results.values()), len(targets)
330
348
 
331
349
  @staticmethod
332
350
  def start(
@@ -334,6 +352,6 @@ class DownloadKakao(DownloadBase):
334
352
  opt_cred: Optional[CredOption],
335
353
  cb: CallbackProtocol,
336
354
  cb_return: CallbackReturn,
337
- ) -> bool:
355
+ ) -> Tuple[int, int]:
338
356
  downloader = DownloadKakao(opt_input, opt_cred, cb, cb_return)
339
357
  return downloader.download_stickers_kakao()
@@ -400,13 +400,13 @@ class DownloadLine(DownloadBase):
400
400
 
401
401
  self.cb.put(f"Combined {i.name.replace('-text.png', '.png')}")
402
402
 
403
- def download_stickers_line(self) -> bool:
403
+ def download_stickers_line(self) -> Tuple[int, int]:
404
404
  url_data = MetadataLine.analyze_url(self.url)
405
405
  if url_data:
406
406
  self.pack_id, self.region, self.is_emoji = url_data
407
407
  else:
408
408
  self.cb.put("Download failed: Unsupported URL format")
409
- return False
409
+ return 0, 0
410
410
 
411
411
  if self.is_emoji:
412
412
  metadata = MetadataLine.get_metadata_sticon(self.pack_id, self.region)
@@ -423,7 +423,7 @@ class DownloadLine(DownloadBase):
423
423
  ) = metadata
424
424
  else:
425
425
  self.cb.put("Download failed: Failed to get metadata")
426
- return False
426
+ return 0, 0
427
427
 
428
428
  MetadataHandler.set_metadata(self.out_dir, title=self.title, author=self.author)
429
429
 
@@ -434,7 +434,7 @@ class DownloadLine(DownloadBase):
434
434
  self.cb.put(f"Downloaded {pack_url}")
435
435
  else:
436
436
  self.cb.put(f"Cannot download {pack_url}")
437
- return False
437
+ return 0, 0
438
438
 
439
439
  if self.is_emoji:
440
440
  self.decompress_emoticon(zip_file)
@@ -458,7 +458,7 @@ class DownloadLine(DownloadBase):
458
458
  self.download_multiple_files(custom_sticker_text_urls, headers=self.headers)
459
459
  self.combine_custom_text()
460
460
 
461
- return True
461
+ return len(self.pack_files), len(self.pack_files)
462
462
 
463
463
  @staticmethod
464
464
  def start(
@@ -466,6 +466,6 @@ class DownloadLine(DownloadBase):
466
466
  opt_cred: Optional[CredOption],
467
467
  cb: CallbackProtocol,
468
468
  cb_return: CallbackReturn,
469
- ) -> bool:
469
+ ) -> Tuple[int, int]:
470
470
  downloader = DownloadLine(opt_input, opt_cred, cb, cb_return)
471
471
  return downloader.download_stickers_line()
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from typing import Dict, Optional
3
+ from typing import Dict, Optional, Tuple
4
4
 
5
5
  import anyio
6
6
  from signalstickers_client.errors import SignalException
@@ -62,23 +62,25 @@ class DownloadSignal(DownloadBase):
62
62
  self.out_dir, title=pack.title, author=pack.author, emoji_dict=emoji_dict
63
63
  )
64
64
 
65
- def download_stickers_signal(self) -> bool:
66
- if "signal.art" not in self.url:
65
+ def download_stickers_signal(self) -> Tuple[int, int]:
66
+ if "signal.art" not in self.url or not self.url.startswith(
67
+ "sgnl://addstickers/"
68
+ ):
67
69
  self.cb.put("Download failed: Unrecognized URL format")
68
- return False
70
+ return 0, 0
69
71
 
70
- pack_id = self.url.split("#pack_id=")[1].split("&pack_key=")[0]
72
+ pack_id = self.url.split("pack_id=")[1].split("&pack_key=")[0]
71
73
  pack_key = self.url.split("&pack_key=")[1]
72
74
 
73
75
  try:
74
76
  pack = anyio.run(DownloadSignal.get_pack, pack_id, pack_key)
75
77
  except SignalException as e:
76
78
  self.cb.put(f"Failed to download pack due to {repr(e)}")
77
- return False
79
+ return 0, 0
78
80
 
79
81
  self.save_stickers(pack)
80
82
 
81
- return True
83
+ return len(pack.stickers), len(pack.stickers)
82
84
 
83
85
  @staticmethod
84
86
  def start(
@@ -86,6 +88,6 @@ class DownloadSignal(DownloadBase):
86
88
  opt_cred: Optional[CredOption],
87
89
  cb: CallbackProtocol,
88
90
  cb_return: CallbackReturn,
89
- ) -> bool:
91
+ ) -> Tuple[int, int]:
90
92
  downloader = DownloadSignal(opt_input, opt_cred, cb, cb_return)
91
93
  return downloader.download_stickers_signal()
@@ -1,15 +1,13 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from typing import Dict, Optional, Union
3
+ from typing import Optional, Tuple
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  import anyio
7
- from telegram import PhotoSize, Sticker, StickerSet
8
- from telegram.error import TelegramError
9
- from telegram.ext import AIORateLimiter, ApplicationBuilder
10
7
 
11
8
  from sticker_convert.downloaders.download_base import DownloadBase
12
9
  from sticker_convert.job_option import CredOption, InputOption
10
+ from sticker_convert.utils.auth.telegram_api import BotAPI, TelegramAPI, TelethonAPI
13
11
  from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
12
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
15
13
 
@@ -18,105 +16,33 @@ class DownloadTelegram(DownloadBase):
18
16
  # def __init__(self, *args: Any, **kwargs: Any) -> None:
19
17
  # super().__init__(*args, **kwargs)
20
18
 
21
- def download_stickers_telegram(self) -> bool:
22
- self.token = ""
23
-
24
- if self.opt_cred:
25
- self.token = self.opt_cred.telegram_token.strip()
26
- if not self.token:
27
- self.cb.put("Download failed: Token required for downloading from telegram")
28
- return False
29
-
19
+ def download_stickers_telegram(self) -> Tuple[int, int]:
30
20
  if not ("telegram.me" in self.url or "t.me" in self.url):
31
21
  self.cb.put("Download failed: Unrecognized URL format")
32
- return False
33
-
34
- self.title = Path(urlparse(self.url).path).name
35
-
36
- self.emoji_dict: Dict[str, str] = {}
22
+ return 0, 0
37
23
 
38
24
  return anyio.run(self.save_stickers)
39
25
 
40
- async def save_stickers(self) -> bool:
41
- timeout = 30
42
- application = ( # type: ignore
43
- ApplicationBuilder()
44
- .token(self.token)
45
- .rate_limiter(AIORateLimiter(max_retries=3))
46
- .connect_timeout(timeout)
47
- .pool_timeout(timeout)
48
- .read_timeout(timeout)
49
- .write_timeout(timeout)
50
- .connection_pool_size(20)
51
- .build()
52
- )
53
-
54
- async with application:
55
- bot = application.bot
56
- try:
57
- sticker_set: StickerSet = await bot.get_sticker_set(
58
- self.title,
59
- read_timeout=timeout,
60
- write_timeout=timeout,
61
- connect_timeout=timeout,
62
- pool_timeout=timeout,
63
- )
64
- except TelegramError as e:
65
- self.cb.put(
66
- f"Failed to download telegram sticker set {self.title} due to: {e}"
67
- )
68
- return False
69
-
70
- self.cb.put(
71
- (
72
- "bar",
73
- None,
74
- {
75
- "set_progress_mode": "determinate",
76
- "steps": len(sticker_set.stickers),
77
- },
78
- )
79
- )
80
-
81
- async def download_sticker(
82
- sticker: Union[PhotoSize, Sticker], f_id: str
83
- ) -> None:
84
- sticker_file = await sticker.get_file(
85
- read_timeout=timeout,
86
- write_timeout=timeout,
87
- connect_timeout=timeout,
88
- pool_timeout=timeout,
89
- )
90
- fpath = sticker_file.file_path
91
- assert fpath is not None
92
- ext = Path(fpath).suffix
93
- f_name = f_id + ext
94
- f_path = Path(self.out_dir, f_name)
95
- await sticker_file.download_to_drive(
96
- custom_path=f_path,
97
- read_timeout=timeout,
98
- write_timeout=timeout,
99
- connect_timeout=timeout,
100
- pool_timeout=timeout,
101
- )
102
- if isinstance(sticker, Sticker) and sticker.emoji is not None:
103
- self.emoji_dict[f_id] = sticker.emoji
104
- self.cb.put(f"Downloaded {f_name}")
105
- if f_id != "cover":
106
- self.cb.put("update_bar")
26
+ async def save_stickers(self) -> Tuple[int, int]:
27
+ tg_api: TelegramAPI
28
+ if self.input_option.endswith("telethon"):
29
+ tg_api = TelethonAPI()
30
+ else:
31
+ tg_api = BotAPI()
107
32
 
108
- async with anyio.create_task_group() as tg:
109
- for num, sticker in enumerate(sticker_set.stickers):
110
- f_id = str(num).zfill(3)
111
- tg.start_soon(download_sticker, sticker, f_id)
33
+ if self.opt_cred is None:
34
+ self.cb.put("Download failed: No credentials")
35
+ return 0, 0
112
36
 
113
- if sticker_set.thumbnail is not None:
114
- tg.start_soon(download_sticker, sticker_set.thumbnail, "cover")
37
+ success = await tg_api.setup(self.opt_cred, False, self.cb, self.cb_return)
38
+ if success is False:
39
+ self.cb.put("Download failed: Invalid credentials")
40
+ return 0, 0
115
41
 
116
- MetadataHandler.set_metadata(
117
- self.out_dir, title=self.title, emoji_dict=self.emoji_dict
118
- )
119
- return True
42
+ title = Path(urlparse(self.url).path).name
43
+ results, emoji_dict = await tg_api.pack_dl(title, self.out_dir)
44
+ MetadataHandler.set_metadata(self.out_dir, title=title, emoji_dict=emoji_dict)
45
+ return sum(results.values()), len(results)
120
46
 
121
47
  @staticmethod
122
48
  def start(
@@ -124,6 +50,6 @@ class DownloadTelegram(DownloadBase):
124
50
  opt_cred: Optional[CredOption],
125
51
  cb: CallbackProtocol,
126
52
  cb_return: CallbackReturn,
127
- ) -> bool:
53
+ ) -> Tuple[int, int]:
128
54
  downloader = DownloadTelegram(opt_input, opt_cred, cb, cb_return)
129
55
  return downloader.download_stickers_telegram()