sticker-convert 2.10.8__py3-none-any.whl → 2.11.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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()