sticker-convert 2.13.3.0__py3-none-any.whl → 2.17.0.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 (93) hide show
  1. sticker_convert/__main__.py +24 -27
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +149 -118
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -330
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +281 -263
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +139 -135
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +250 -235
  12. sticker_convert/{utils/auth → auth}/telegram_api.py +736 -675
  13. sticker_convert/cli.py +623 -608
  14. sticker_convert/converter.py +1093 -1084
  15. sticker_convert/definitions.py +4 -0
  16. sticker_convert/downloaders/download_band.py +111 -110
  17. sticker_convert/downloaders/download_base.py +171 -166
  18. sticker_convert/downloaders/download_discord.py +92 -91
  19. sticker_convert/downloaders/download_kakao.py +417 -404
  20. sticker_convert/downloaders/download_line.py +484 -475
  21. sticker_convert/downloaders/download_ogq.py +80 -79
  22. sticker_convert/downloaders/download_signal.py +108 -105
  23. sticker_convert/downloaders/download_telegram.py +56 -55
  24. sticker_convert/downloaders/download_viber.py +121 -120
  25. sticker_convert/gui.py +788 -873
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -166
  27. sticker_convert/gui_components/frames/config_frame.py +156 -113
  28. sticker_convert/gui_components/frames/control_frame.py +32 -30
  29. sticker_convert/gui_components/frames/cred_frame.py +232 -233
  30. sticker_convert/gui_components/frames/input_frame.py +139 -137
  31. sticker_convert/gui_components/frames/output_frame.py +112 -110
  32. sticker_convert/gui_components/frames/right_clicker.py +25 -23
  33. sticker_convert/gui_components/windows/advanced_compression_window.py +757 -757
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -82
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -321
  37. sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
  38. sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -89
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -168
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/README.md +10 -10
  42. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  43. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  50. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  51. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  52. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  53. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  54. sticker_convert/job.py +166 -130
  55. sticker_convert/job_option.py +1 -0
  56. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  57. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  58. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  59. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  60. sticker_convert/py.typed +0 -0
  61. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  62. sticker_convert/resources/help.ja_JP.json +88 -0
  63. sticker_convert/resources/help.json +10 -7
  64. sticker_convert/resources/help.zh_CN.json +88 -0
  65. sticker_convert/resources/help.zh_TW.json +88 -0
  66. sticker_convert/resources/input.ja_JP.json +74 -0
  67. sticker_convert/resources/input.json +121 -121
  68. sticker_convert/resources/input.zh_CN.json +74 -0
  69. sticker_convert/resources/input.zh_TW.json +74 -0
  70. sticker_convert/resources/output.ja_JP.json +38 -0
  71. sticker_convert/resources/output.zh_CN.json +38 -0
  72. sticker_convert/resources/output.zh_TW.json +38 -0
  73. sticker_convert/uploaders/compress_wastickers.py +186 -177
  74. sticker_convert/uploaders/upload_base.py +44 -35
  75. sticker_convert/uploaders/upload_signal.py +218 -203
  76. sticker_convert/uploaders/upload_telegram.py +353 -338
  77. sticker_convert/uploaders/upload_viber.py +178 -169
  78. sticker_convert/uploaders/xcode_imessage.py +295 -286
  79. sticker_convert/utils/callback.py +238 -6
  80. sticker_convert/utils/emoji.py +16 -4
  81. sticker_convert/utils/files/json_resources_loader.py +24 -19
  82. sticker_convert/utils/files/metadata_handler.py +3 -3
  83. sticker_convert/utils/translate.py +108 -0
  84. sticker_convert/utils/url_detect.py +40 -37
  85. sticker_convert/version.py +1 -1
  86. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +89 -74
  87. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/RECORD +91 -74
  88. sticker_convert/utils/auth/telethon_setup.py +0 -97
  89. sticker_convert/utils/singletons.py +0 -18
  90. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +0 -0
  91. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  92. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/licenses/LICENSE +0 -0
  93. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -1,675 +1,736 @@
1
- #!/usr/bin/env python3
2
- import re
3
- import time
4
- from collections import defaultdict
5
- from pathlib import Path
6
- from typing import Any, Dict, List, Optional, Protocol, Tuple, Union, cast
7
-
8
- import anyio
9
- from telegram import InputSticker, PhotoSize, Sticker
10
- from telegram import StickerSet as TGStickerSet
11
- from telegram.error import BadRequest, TelegramError
12
- from telegram.ext import AIORateLimiter, ApplicationBuilder
13
- from telethon.errors.rpcerrorlist import StickersetInvalidError # type: ignore
14
- from telethon.functions import messages # type: ignore
15
- from telethon.tl.types.messages import StickerSet as TLStickerSet # type: ignore
16
- from telethon.types import DocumentAttributeFilename, InputStickerSetShortName, InputStickerSetThumb, Message, TypeDocument # type: ignore
17
-
18
- from sticker_convert.job_option import CredOption
19
- from sticker_convert.utils.auth.telethon_setup import TelethonSetup
20
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
21
-
22
- # sticker_path: Path, sticker_bytes: bytes, emoji_list: List[str], sticker_format: str
23
- TelegramSticker = Tuple[Path, bytes, List[str], str]
24
-
25
-
26
- class TelegramAPI(Protocol):
27
- async def setup(
28
- self,
29
- opt_cred: CredOption,
30
- is_upload: bool,
31
- cb: CallbackProtocol,
32
- cb_return: CallbackReturn,
33
- ) -> bool: ...
34
- async def exit(self) -> None: ...
35
- async def set_upload_pack_type(self, is_emoji: bool) -> None: ...
36
- async def set_upload_pack_short_name(self, pack_title: str) -> str: ...
37
- async def check_pack_exist(self) -> bool: ...
38
- async def pack_del(self) -> bool: ...
39
- async def pack_new(
40
- self, stickers_list: List[TelegramSticker], sticker_type: str
41
- ) -> Tuple[int, int]: ...
42
- async def pack_add(
43
- self, stickers_list: List[TelegramSticker], sticker_type: str
44
- ) -> Tuple[int, int]: ...
45
- async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool: ...
46
- async def get_pack_url(self) -> str: ...
47
- async def pack_dl(
48
- self, pack_short_name: str, out_dir: Path
49
- ) -> Tuple[Dict[str, bool], Dict[str, str]]: ...
50
-
51
-
52
- class BotAPI(TelegramAPI):
53
- async def setup(
54
- self,
55
- opt_cred: CredOption,
56
- is_upload: bool,
57
- cb: CallbackProtocol,
58
- cb_return: CallbackReturn,
59
- ) -> bool:
60
- self.timeout = 30
61
- self.cb = cb
62
-
63
- if is_upload and not (opt_cred.telegram_token and opt_cred.telegram_userid):
64
- self.cb.put("Token and userid required for uploading to telegram")
65
- return False
66
- elif is_upload is False and not opt_cred.telegram_token:
67
- self.cb.put("Token required for downloading from telegram")
68
- return False
69
-
70
- if opt_cred.telegram_userid.isnumeric():
71
- self.telegram_userid = int(opt_cred.telegram_userid)
72
- else:
73
- self.cb.put("Invalid userid, should contain numbers only")
74
- return False
75
-
76
- self.application = ( # type: ignore
77
- ApplicationBuilder()
78
- .token(opt_cred.telegram_token)
79
- .rate_limiter(AIORateLimiter(overall_max_rate=4, max_retries=3))
80
- .connect_timeout(self.timeout)
81
- .pool_timeout(self.timeout)
82
- .read_timeout(self.timeout)
83
- .write_timeout(self.timeout)
84
- .build()
85
- )
86
- await self.application.initialize()
87
-
88
- return True
89
-
90
- async def exit(self) -> None:
91
- await self.application.shutdown()
92
-
93
- async def set_upload_pack_short_name(self, pack_title: str) -> str:
94
- self.pack_title = pack_title
95
- bot_name = self.application.bot.name
96
- self.pack_short_name = (
97
- pack_title.replace(" ", "_") + "_by_" + bot_name.replace("@", "")
98
- )
99
- self.pack_short_name = re.sub(
100
- "[^0-9a-zA-Z]+", "_", self.pack_short_name
101
- ) # name used in url, only alphanum and underscore only
102
- return self.pack_short_name
103
-
104
- async def set_upload_pack_type(self, is_emoji: bool) -> None:
105
- self.is_emoji = is_emoji
106
-
107
- async def check_pack_exist(self) -> bool:
108
- sticker_set: Any = None
109
- try:
110
- sticker_set = await self.application.bot.get_sticker_set(
111
- self.pack_short_name,
112
- read_timeout=30,
113
- write_timeout=30,
114
- connect_timeout=30,
115
- pool_timeout=30,
116
- )
117
- except TelegramError:
118
- pass
119
-
120
- if sticker_set is not None:
121
- return True
122
- return False
123
-
124
- async def pack_del(self) -> bool:
125
- try:
126
- await self.application.bot.delete_sticker_set(self.pack_short_name)
127
- except BadRequest as e:
128
- msg = f"Cannot delete sticker set {self.pack_short_name} due to {e}"
129
- if str(e) == "Stickerpack_not_found":
130
- msg += "\nHint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
131
- self.cb.put(msg)
132
- return False
133
- except TelegramError as e:
134
- self.cb.put(f"Cannot delete sticker set {self.pack_short_name} due to {e}")
135
- return False
136
- return True
137
-
138
- async def pack_new(
139
- self, stickers_list: List[TelegramSticker], sticker_type: str
140
- ) -> Tuple[int, int]:
141
- init_input_stickers: List[InputSticker] = []
142
- for i in stickers_list[:50]:
143
- init_input_stickers.append(
144
- InputSticker(
145
- sticker=i[1],
146
- emoji_list=i[2],
147
- format=i[3],
148
- )
149
- )
150
-
151
- try:
152
- self.cb.put(
153
- f"Creating pack and bulk uploading {len(init_input_stickers)} stickers of {self.pack_short_name}"
154
- )
155
- await self.application.bot.create_new_sticker_set(
156
- self.telegram_userid,
157
- self.pack_short_name,
158
- self.pack_title,
159
- init_input_stickers,
160
- sticker_type,
161
- )
162
- self.cb.put(
163
- f"Created pack and bulk uploaded {len(init_input_stickers)} stickers of {self.pack_short_name}"
164
- )
165
- _, success_add = await self.pack_add(stickers_list[50:], sticker_type)
166
- return len(stickers_list), len(init_input_stickers) + success_add
167
- except TelegramError as e:
168
- self.cb.put(
169
- f"Cannot create pack and bulk upload {len(init_input_stickers)} stickers of {self.pack_short_name} due to {e}"
170
- )
171
- return len(stickers_list), 0
172
-
173
- async def pack_add(
174
- self, stickers_list: List[TelegramSticker], sticker_type: str
175
- ) -> Tuple[int, int]:
176
- stickers_ok = 0
177
- self.cb.put(
178
- (
179
- "bar",
180
- None,
181
- {
182
- "set_progress_mode": "determinate",
183
- "steps": len(stickers_list),
184
- },
185
- )
186
- )
187
- for i in stickers_list:
188
- input_sticker = InputSticker(
189
- sticker=i[1],
190
- emoji_list=i[2],
191
- format=i[3],
192
- )
193
- try:
194
- # We could use tg.start_soon() here
195
- # But this would disrupt the order of stickers
196
- await self.application.bot.add_sticker_to_set(
197
- self.telegram_userid,
198
- self.pack_short_name,
199
- input_sticker,
200
- )
201
- self.cb.put(f"Uploaded sticker {i[0]} of {self.pack_short_name}")
202
- stickers_ok += 1
203
- except BadRequest as e:
204
- self.cb.put(
205
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {e}"
206
- )
207
- if str(e) == "Stickerpack_not_found":
208
- self.cb.put(
209
- "Hint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
210
- )
211
- except TelegramError as e:
212
- self.cb.put(
213
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {e}"
214
- )
215
- self.cb.put("update_bar")
216
-
217
- self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
218
- return len(stickers_list), stickers_ok
219
-
220
- async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool:
221
- try:
222
- self.cb.put(f"Uploading cover (thumbnail) of pack {self.pack_short_name}")
223
- await self.application.bot.set_sticker_set_thumbnail(
224
- self.pack_short_name,
225
- self.telegram_userid,
226
- thumbnail[3],
227
- thumbnail[1],
228
- )
229
- self.cb.put(f"Uploaded cover (thumbnail) of pack {self.pack_short_name}")
230
- return True
231
- except TelegramError as e:
232
- self.cb.put(
233
- f"Cannot upload cover (thumbnail) of pack {self.pack_short_name} due to {e}"
234
- )
235
- return False
236
-
237
- async def get_pack_url(self) -> str:
238
- if self.is_emoji:
239
- return f"https://t.me/addemoji/{self.pack_short_name}"
240
- else:
241
- return f"https://t.me/addstickers/{self.pack_short_name}"
242
-
243
- async def _download_sticker(
244
- self,
245
- sticker: Union[PhotoSize, Sticker],
246
- f_id: str,
247
- out_dir: Path,
248
- results: Dict[str, bool],
249
- emoji_dict: Dict[str, str],
250
- ) -> None:
251
- try:
252
- sticker_file = await sticker.get_file(
253
- read_timeout=self.timeout,
254
- write_timeout=self.timeout,
255
- connect_timeout=self.timeout,
256
- pool_timeout=self.timeout,
257
- )
258
- except TelegramError as e:
259
- self.cb.put(f"Failed to download {f_id}: {str(e)}")
260
- results[f_id] = False
261
- return
262
- fpath = sticker_file.file_path
263
- assert fpath is not None
264
- ext = Path(fpath).suffix
265
- f_name = f_id + ext
266
- f_path = Path(out_dir, f_name)
267
- await sticker_file.download_to_drive(
268
- custom_path=f_path,
269
- read_timeout=self.timeout,
270
- write_timeout=self.timeout,
271
- connect_timeout=self.timeout,
272
- pool_timeout=self.timeout,
273
- )
274
- if isinstance(sticker, Sticker) and sticker.emoji is not None:
275
- emoji_dict[f_id] = sticker.emoji
276
- self.cb.put(f"Downloaded {f_name}")
277
- results[f_id] = True
278
- if f_id != "cover":
279
- self.cb.put("update_bar")
280
-
281
- async def pack_dl(
282
- self, pack_short_name: str, out_dir: Path
283
- ) -> Tuple[Dict[str, bool], Dict[str, str]]:
284
- results: Dict[str, bool] = {}
285
- emoji_dict: Dict[str, str] = {}
286
-
287
- try:
288
- sticker_set: TGStickerSet = await self.application.bot.get_sticker_set(
289
- pack_short_name,
290
- read_timeout=self.timeout,
291
- write_timeout=self.timeout,
292
- connect_timeout=self.timeout,
293
- pool_timeout=self.timeout,
294
- )
295
- except TelegramError as e:
296
- self.cb.put(
297
- f"Failed to download telegram sticker set {pack_short_name} due to: {e}"
298
- )
299
- return results, emoji_dict
300
-
301
- self.cb.put(
302
- (
303
- "bar",
304
- None,
305
- {
306
- "set_progress_mode": "determinate",
307
- "steps": len(sticker_set.stickers),
308
- },
309
- )
310
- )
311
-
312
- async with anyio.create_task_group() as tg:
313
- for num, sticker in enumerate(sticker_set.stickers):
314
- f_id = str(num).zfill(3)
315
- tg.start_soon(
316
- self._download_sticker, sticker, f_id, out_dir, results, emoji_dict
317
- )
318
-
319
- if sticker_set.thumbnail is not None:
320
- results_thumb: Dict[str, bool] = {}
321
- tg.start_soon(
322
- self._download_sticker,
323
- sticker_set.thumbnail,
324
- "cover",
325
- out_dir,
326
- results_thumb,
327
- emoji_dict,
328
- )
329
-
330
- return results, emoji_dict
331
-
332
-
333
- class TelethonAPI(TelegramAPI):
334
- async def setup(
335
- self,
336
- opt_cred: CredOption,
337
- is_upload: bool,
338
- cb: CallbackProtocol,
339
- cb_return: CallbackReturn,
340
- ) -> bool:
341
- self.opt_cred = opt_cred
342
- self.cb = cb
343
- self.cb_return = cb_return
344
-
345
- success, client, _, _ = await TelethonSetup(
346
- self.opt_cred, self.cb_ask_str
347
- ).start_async()
348
-
349
- if success is True and client is not None:
350
- self.client = client
351
- return success
352
-
353
- async def exit(self) -> None:
354
- self.client.disconnect()
355
-
356
- def cb_ask_str(
357
- self, msg: Optional[str] = None, initialvalue: Optional[str] = None
358
- ) -> str:
359
- self.cb.put(("ask_str", (msg,), None))
360
- response = self.cb_return.get_response()
361
-
362
- assert isinstance(response, str)
363
- return response
364
-
365
- async def set_upload_pack_short_name(self, pack_title: str) -> str:
366
- self.pack_title = pack_title
367
- self.pack_short_name = re.sub(
368
- "[^0-9a-zA-Z]+", "_", pack_title
369
- ) # name used in url, only alphanum and underscore only
370
- return self.pack_short_name
371
-
372
- async def set_upload_pack_type(self, is_emoji: bool) -> None:
373
- self.is_emoji = is_emoji
374
-
375
- async def check_pack_exist(self) -> bool:
376
- try:
377
- await self.client(
378
- messages.GetStickerSetRequest(
379
- InputStickerSetShortName(self.pack_short_name), 0
380
- )
381
- )
382
- except StickersetInvalidError:
383
- return False
384
-
385
- return True
386
-
387
- async def _send_and_recv(self, msg: Union[str, Path]) -> str:
388
- if isinstance(msg, str):
389
- sent_message = await self.client.send_message("Stickers", msg)
390
- else:
391
- sent_message = cast(
392
- Message,
393
- await self.client.send_file("Stickers", msg, force_document=True), # type: ignore
394
- )
395
-
396
- for _ in range(5):
397
- # https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
398
- # In a single chat, avoid sending more than one message per second.
399
- time.sleep(1)
400
- last_message = cast(
401
- List[Message],
402
- await self.client.get_messages("Stickers", 1), # type: ignore
403
- )[0]
404
- if sent_message.id != last_message.id:
405
- return last_message.message
406
-
407
- return "timeout"
408
-
409
- async def pack_del(self) -> bool:
410
- msg_fail = "Cannot delete pack of {} due to {}"
411
- if self.is_emoji:
412
- repl = await self._send_and_recv("/delemoji")
413
- else:
414
- repl = await self._send_and_recv("/delpack")
415
- if repl != "Choose the sticker set you want to delete.":
416
- self.cb.put(msg_fail.format(self.pack_short_name, repl))
417
- return False
418
- repl = await self._send_and_recv(self.pack_short_name)
419
- if "Yes, I am totally sure." not in repl:
420
- self.cb.put(msg_fail.format(self.pack_short_name, repl))
421
- return False
422
- repl = await self._send_and_recv("Yes, I am totally sure.")
423
- if "Done!" not in repl:
424
- self.cb.put(msg_fail.format(self.pack_short_name, repl))
425
- return False
426
-
427
- return True
428
-
429
- async def pack_new(
430
- self, stickers_list: List[TelegramSticker], sticker_type: str
431
- ) -> Tuple[int, int]:
432
- stickers_ok = 0
433
- if self.is_emoji:
434
- repl = await self._send_and_recv("/newpack")
435
- elif stickers_list[0][3] == "static":
436
- repl = await self._send_and_recv("/newsticker")
437
- elif stickers_list[0][3] == "video":
438
- repl = await self._send_and_recv("/newvideo")
439
- elif stickers_list[0][3] == "animated":
440
- repl = await self._send_and_recv("/newanimated")
441
- else:
442
- self.cb.put(
443
- f"Cannot upload any sticker to {self.pack_short_name} due to invalid sticker format {stickers_list[0][3]}"
444
- )
445
- return len(stickers_list), 0
446
- if "Yay!" not in repl:
447
- self.cb.put(f"Cannot upload any sticker due to {repl}")
448
- return len(stickers_list), 0
449
-
450
- if self.is_emoji:
451
- repl = await self._send_and_recv(
452
- f"{stickers_list[0][3].capitalize()} emoji"
453
- )
454
- if "Yay!" not in repl:
455
- self.cb.put(f"Cannot upload any sticker due to {repl}")
456
- return len(stickers_list), 0
457
-
458
- repl = await self._send_and_recv(self.pack_title)
459
- if "Alright!" not in repl:
460
- self.cb.put(f"Cannot upload any sticker due to {repl}")
461
- return len(stickers_list), 0
462
- self.cb.put(
463
- (
464
- "bar",
465
- None,
466
- {
467
- "set_progress_mode": "determinate",
468
- "steps": len(stickers_list),
469
- },
470
- )
471
- )
472
- for i in stickers_list:
473
- repl = await self._send_and_recv(i[0])
474
- if "Thanks!" not in repl:
475
- self.cb.put(
476
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {repl}"
477
- )
478
- self.cb.put("update_bar")
479
- continue
480
- repl = await self._send_and_recv("".join(i[2]))
481
- if "Congratulations." not in repl:
482
- self.cb.put(
483
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {repl}"
484
- )
485
- self.cb.put("update_bar")
486
- continue
487
- stickers_ok += 1
488
- self.cb.put("update_bar")
489
- repl = await self._send_and_recv("/publish")
490
- if "icon" not in repl:
491
- self.cb.put(f"Cannot upload pack {self.pack_short_name} due to {repl}")
492
- return len(stickers_list), 0
493
- repl = await self._send_and_recv("/skip")
494
- if "Please provide a short name" not in repl:
495
- self.cb.put(f"Cannot upload pack {self.pack_short_name} due to {repl}")
496
- return len(stickers_list), 0
497
- repl = await self._send_and_recv(self.pack_short_name)
498
- if "Kaboom!" not in repl:
499
- self.cb.put(f"Cannot upload pack {self.pack_short_name} due to {repl}")
500
- return len(stickers_list), 0
501
-
502
- self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
503
-
504
- return len(stickers_list), stickers_ok
505
-
506
- async def pack_add(
507
- self, stickers_list: List[TelegramSticker], sticker_type: str
508
- ) -> Tuple[int, int]:
509
- stickers_ok = 0
510
- if self.is_emoji:
511
- repl = await self._send_and_recv("/addemoji")
512
- else:
513
- repl = await self._send_and_recv("/addsticker")
514
- if "Choose" not in repl:
515
- self.cb.put(
516
- f"Cannot upload any sticker to {self.pack_short_name} due to {repl}"
517
- )
518
- return len(stickers_list), 0
519
- repl = await self._send_and_recv(self.pack_short_name)
520
- if "Alright!" not in repl:
521
- self.cb.put(
522
- f"Cannot upload any sticker to {self.pack_short_name} due to {repl}"
523
- )
524
- return len(stickers_list), 0
525
-
526
- self.cb.put(
527
- (
528
- "bar",
529
- None,
530
- {
531
- "set_progress_mode": "determinate",
532
- "steps": len(stickers_list),
533
- },
534
- )
535
- )
536
- for i in stickers_list:
537
- repl = await self._send_and_recv(i[0])
538
- if "Thanks!" not in repl:
539
- self.cb.put(
540
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {repl}"
541
- )
542
- self.cb.put("update_bar")
543
- continue
544
- repl = await self._send_and_recv("".join(i[2]))
545
- if "There we go." not in repl:
546
- self.cb.put(
547
- f"Cannot upload sticker {i[0]} of {self.pack_short_name} due to {repl}"
548
- )
549
- self.cb.put("update_bar")
550
- continue
551
- self.cb.put("update_bar")
552
- stickers_ok += 1
553
-
554
- self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
555
-
556
- repl = await self._send_and_recv("/done")
557
- if "OK" not in repl:
558
- self.cb.put(
559
- f"Cannot upload any sticker to {self.pack_short_name} due to {repl}"
560
- )
561
- return len(stickers_list), 0
562
-
563
- return len(stickers_list), stickers_ok
564
-
565
- async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool:
566
- repl = await self._send_and_recv("/setpackicon")
567
- if "OK" not in repl:
568
- self.cb.put(
569
- f"Cannot set pack icon for {self.pack_short_name} due to {repl}"
570
- )
571
- return False
572
- repl = await self._send_and_recv(thumbnail[0])
573
- if "Enjoy!" not in repl:
574
- self.cb.put(
575
- f"Cannot set pack icon for {self.pack_short_name} due to {repl}"
576
- )
577
- return False
578
- return True
579
-
580
- async def get_pack_url(self) -> str:
581
- if self.is_emoji:
582
- return f"https://t.me/addemoji/{self.pack_short_name}"
583
- else:
584
- return f"https://t.me/addstickers/{self.pack_short_name}"
585
-
586
- async def _download_sticker(
587
- self,
588
- sticker: TypeDocument,
589
- f_id: str,
590
- out_dir: Path,
591
- id_to_emoji: Dict[int, str],
592
- emoji_dict: Dict[str, str],
593
- results: Dict[str, bool],
594
- ) -> None:
595
- fpath_attr = [
596
- attr
597
- for attr in sticker.attributes # type: ignore
598
- if isinstance(attr, DocumentAttributeFilename)
599
- ]
600
- assert len(fpath_attr) > 0
601
- fpath = fpath_attr[0].file_name
602
- ext = Path(fpath).suffix
603
- f_name = f_id + ext
604
- f_path = Path(out_dir, f_name)
605
-
606
- try:
607
- await self.client.download_media(sticker, file=f_path) # type: ignore
608
- except Exception as e:
609
- self.cb.put(f"Failed to download {f_id}: {str(e)}")
610
- results[f_id] = False
611
- return
612
-
613
- emoji_dict[f_id] = id_to_emoji[sticker.id]
614
- self.cb.put(f"Downloaded {f_name}")
615
- results[f_id] = True
616
- self.cb.put("update_bar")
617
-
618
- async def pack_dl(
619
- self, pack_short_name: str, out_dir: Path
620
- ) -> Tuple[Dict[str, bool], Dict[str, str]]:
621
- results: Dict[str, bool] = {}
622
- emoji_dict: Dict[str, str] = {}
623
- id_to_emoji: Dict[int, str] = defaultdict(str)
624
-
625
- sticker_set = cast(
626
- TLStickerSet,
627
- await self.client(
628
- messages.GetStickerSetRequest(
629
- InputStickerSetShortName(pack_short_name), 0
630
- )
631
- ),
632
- )
633
-
634
- self.cb.put(
635
- (
636
- "bar",
637
- None,
638
- {
639
- "set_progress_mode": "determinate",
640
- "steps": len(sticker_set.documents),
641
- },
642
- )
643
- )
644
-
645
- for pack in sticker_set.packs:
646
- for document_id in pack.documents:
647
- id_to_emoji[document_id] += pack.emoticon
648
-
649
- ext = ""
650
- async with anyio.create_task_group() as tg:
651
- for num, sticker in enumerate(sticker_set.documents):
652
- f_id = str(num).zfill(3)
653
- tg.start_soon(
654
- self._download_sticker,
655
- sticker,
656
- f_id,
657
- out_dir,
658
- id_to_emoji,
659
- emoji_dict,
660
- results,
661
- )
662
-
663
- if sticker_set.set.thumb_version and ext:
664
- try:
665
- await self.client.download_file( # type: ignore
666
- InputStickerSetThumb(
667
- InputStickerSetShortName(pack_short_name),
668
- thumb_version=sticker_set.set.thumb_version,
669
- ),
670
- f"cover{ext}",
671
- )
672
- except Exception as e:
673
- self.cb.put(f"Failed to download cover{ext}: {str(e)}")
674
-
675
- return results, emoji_dict
1
+ #!/usr/bin/env python3
2
+ import re
3
+ import time
4
+ from collections import defaultdict
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Tuple, Union, cast
7
+
8
+ import anyio
9
+ from telegram import InputSticker, PhotoSize, Sticker
10
+ from telegram import StickerSet as TGStickerSet
11
+ from telegram.error import BadRequest, TelegramError
12
+ from telegram.ext import AIORateLimiter, ApplicationBuilder
13
+ from telethon.errors.rpcerrorlist import StickersetInvalidError # type: ignore
14
+ from telethon.functions import messages # type: ignore
15
+ from telethon.tl.types.messages import StickerSet as TLStickerSet # type: ignore
16
+ from telethon.types import DocumentAttributeFilename, InputStickerSetShortName, InputStickerSetThumb, Message, TypeDocument # type: ignore
17
+
18
+ from sticker_convert.auth.auth_telethon import AuthTelethon
19
+ from sticker_convert.job_option import CredOption
20
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
21
+ from sticker_convert.utils.translate import I
22
+
23
+ # sticker_path: Path, sticker_bytes: bytes, emoji_list: List[str], sticker_format: str
24
+ TelegramSticker = Tuple[Path, bytes, List[str], str]
25
+
26
+
27
+ class TelegramAPI:
28
+ def __init__(self):
29
+ self.MSG_FAIL_ALL = I("Cannot upload any sticker. Reason: {}")
30
+ self.MSG_FAIL_PACK = I("Cannot upload pack {}. Reason: {}")
31
+ self.MSG_FAIL_DEL = I("Cannot delete pack of {}. Reason: {}")
32
+ self.MSG_FAIL_STICKER = I(
33
+ "Cannot upload sticker {sticker} of {pack}. Reason: {reason}"
34
+ )
35
+ self.MSG_FAIL_PACK_ICON = I("Cannot set pack icon for {}. Reason: {}")
36
+
37
+ async def setup(
38
+ self,
39
+ opt_cred: CredOption,
40
+ is_upload: bool,
41
+ cb: CallbackProtocol,
42
+ cb_return: CallbackReturn,
43
+ ) -> bool:
44
+ raise NotImplementedError
45
+
46
+ async def exit(self) -> None:
47
+ raise NotImplementedError
48
+
49
+ async def set_upload_pack_type(self, is_emoji: bool) -> None:
50
+ raise NotImplementedError
51
+
52
+ async def set_upload_pack_short_name(self, pack_title: str) -> str:
53
+ raise NotImplementedError
54
+
55
+ async def check_pack_exist(self) -> bool:
56
+ raise NotImplementedError
57
+
58
+ async def pack_del(self) -> bool:
59
+ raise NotImplementedError
60
+
61
+ async def pack_new(
62
+ self, stickers_list: List[TelegramSticker], sticker_type: str
63
+ ) -> Tuple[int, int]:
64
+ raise NotImplementedError
65
+
66
+ async def pack_add(
67
+ self, stickers_list: List[TelegramSticker], sticker_type: str
68
+ ) -> Tuple[int, int]:
69
+ raise NotImplementedError
70
+
71
+ async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool:
72
+ raise NotImplementedError
73
+
74
+ async def get_pack_url(self) -> str:
75
+ raise NotImplementedError
76
+
77
+ async def pack_dl(
78
+ self, pack_short_name: str, out_dir: Path
79
+ ) -> Tuple[Dict[str, bool], Dict[str, str]]:
80
+ raise NotImplementedError
81
+
82
+
83
+ class BotAPI(TelegramAPI):
84
+ def __init__(self, *args: Any, **kwargs: Any):
85
+ super().__init__(*args, **kwargs)
86
+
87
+ async def setup(
88
+ self,
89
+ opt_cred: CredOption,
90
+ is_upload: bool,
91
+ cb: CallbackProtocol,
92
+ cb_return: CallbackReturn,
93
+ ) -> bool:
94
+ self.timeout = 30
95
+ self.cb = cb
96
+
97
+ if is_upload and not (opt_cred.telegram_token and opt_cred.telegram_userid):
98
+ self.cb.put(I("Token and userid required for uploading to telegram"))
99
+ return False
100
+ elif is_upload is False and not opt_cred.telegram_token:
101
+ self.cb.put(I("Token required for downloading from telegram"))
102
+ return False
103
+
104
+ if opt_cred.telegram_userid:
105
+ if opt_cred.telegram_userid.isnumeric():
106
+ self.telegram_userid = int(opt_cred.telegram_userid)
107
+ else:
108
+ self.cb.put(I("Invalid userid, should contain numbers only"))
109
+ return False
110
+
111
+ self.application = ( # type: ignore
112
+ ApplicationBuilder()
113
+ .token(opt_cred.telegram_token)
114
+ .rate_limiter(AIORateLimiter(overall_max_rate=4, max_retries=3))
115
+ .connect_timeout(self.timeout)
116
+ .pool_timeout(self.timeout)
117
+ .read_timeout(self.timeout)
118
+ .write_timeout(self.timeout)
119
+ .build()
120
+ )
121
+ await self.application.initialize()
122
+
123
+ return True
124
+
125
+ async def exit(self) -> None:
126
+ await self.application.shutdown()
127
+
128
+ async def set_upload_pack_short_name(self, pack_title: str) -> str:
129
+ self.pack_title = pack_title
130
+ bot_name = self.application.bot.name
131
+ self.pack_short_name = (
132
+ pack_title.replace(" ", "_") + "_by_" + bot_name.replace("@", "")
133
+ )
134
+ self.pack_short_name = re.sub(
135
+ "[^0-9a-zA-Z]+", "_", self.pack_short_name
136
+ ) # name used in url, only alphanum and underscore only
137
+ return self.pack_short_name
138
+
139
+ async def set_upload_pack_type(self, is_emoji: bool) -> None:
140
+ self.is_emoji = is_emoji
141
+
142
+ async def check_pack_exist(self) -> bool:
143
+ sticker_set: Any = None
144
+ try:
145
+ sticker_set = await self.application.bot.get_sticker_set(
146
+ self.pack_short_name,
147
+ read_timeout=30,
148
+ write_timeout=30,
149
+ connect_timeout=30,
150
+ pool_timeout=30,
151
+ )
152
+ except TelegramError:
153
+ pass
154
+
155
+ if sticker_set is not None:
156
+ return True
157
+ return False
158
+
159
+ async def pack_del(self) -> bool:
160
+ try:
161
+ await self.application.bot.delete_sticker_set(self.pack_short_name)
162
+ except BadRequest as e:
163
+ msg = I("Cannot delete sticker set {}. Reason: {}").format(
164
+ self.pack_short_name, e
165
+ )
166
+ if str(e) == "Stickerpack_not_found":
167
+ msg += I(
168
+ "\nHint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
169
+ )
170
+ self.cb.put(msg)
171
+ return False
172
+ except TelegramError as e:
173
+ self.cb.put(
174
+ I("Cannot delete sticker set {}. Reason: {}").format(
175
+ self.pack_short_name, e
176
+ )
177
+ )
178
+ return False
179
+ return True
180
+
181
+ async def pack_new(
182
+ self, stickers_list: List[TelegramSticker], sticker_type: str
183
+ ) -> Tuple[int, int]:
184
+ init_input_stickers: List[InputSticker] = []
185
+ for i in stickers_list[:50]:
186
+ init_input_stickers.append(
187
+ InputSticker(
188
+ sticker=i[1],
189
+ emoji_list=i[2],
190
+ format=i[3],
191
+ )
192
+ )
193
+
194
+ try:
195
+ self.cb.put(
196
+ I("Creating pack and bulk uploading {} stickers of {}").format(
197
+ len(init_input_stickers), self.pack_short_name
198
+ )
199
+ )
200
+ await self.application.bot.create_new_sticker_set(
201
+ self.telegram_userid,
202
+ self.pack_short_name,
203
+ self.pack_title,
204
+ init_input_stickers,
205
+ sticker_type,
206
+ )
207
+ self.cb.put(
208
+ I("Created pack and bulk uploaded {} stickers of {}").format(
209
+ len(init_input_stickers), self.pack_short_name
210
+ )
211
+ )
212
+ _, success_add = await self.pack_add(stickers_list[50:], sticker_type)
213
+ return len(stickers_list), len(init_input_stickers) + success_add
214
+ except TelegramError as e:
215
+ self.cb.put(
216
+ I(
217
+ "Cannot create pack and bulk upload {} stickers of {}. Reason: {}"
218
+ ).format(len(init_input_stickers), self.pack_short_name, e)
219
+ )
220
+ return len(stickers_list), 0
221
+
222
+ async def pack_add(
223
+ self, stickers_list: List[TelegramSticker], sticker_type: str
224
+ ) -> Tuple[int, int]:
225
+ stickers_ok = 0
226
+ self.cb.put(
227
+ (
228
+ "bar",
229
+ None,
230
+ {
231
+ "set_progress_mode": "determinate",
232
+ "steps": len(stickers_list),
233
+ },
234
+ )
235
+ )
236
+ for i in stickers_list:
237
+ input_sticker = InputSticker(
238
+ sticker=i[1],
239
+ emoji_list=i[2],
240
+ format=i[3],
241
+ )
242
+ try:
243
+ # We could use tg.start_soon() here
244
+ # But this would disrupt the order of stickers
245
+ await self.application.bot.add_sticker_to_set(
246
+ self.telegram_userid,
247
+ self.pack_short_name,
248
+ input_sticker,
249
+ )
250
+ self.cb.put(
251
+ I("Uploaded sticker {} of {}").format(i[0], self.pack_short_name)
252
+ )
253
+ stickers_ok += 1
254
+ except BadRequest as e:
255
+ self.cb.put(
256
+ self.MSG_FAIL_STICKER.format(
257
+ sticker=i[0], pack=self.pack_short_name, reason=e
258
+ )
259
+ )
260
+ if str(e) == "Stickerpack_not_found":
261
+ self.cb.put(
262
+ I(
263
+ "Hint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
264
+ )
265
+ )
266
+ except TelegramError as e:
267
+ self.cb.put(
268
+ self.MSG_FAIL_STICKER.format(
269
+ sticker=i[0], pack=self.pack_short_name, reason=e
270
+ )
271
+ )
272
+ self.cb.put("update_bar")
273
+
274
+ self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
275
+ return len(stickers_list), stickers_ok
276
+
277
+ async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool:
278
+ try:
279
+ self.cb.put(
280
+ I("Uploading cover (thumbnail) of pack {}").format(self.pack_short_name)
281
+ )
282
+ await self.application.bot.set_sticker_set_thumbnail(
283
+ self.pack_short_name,
284
+ self.telegram_userid,
285
+ thumbnail[3],
286
+ thumbnail[1],
287
+ )
288
+ self.cb.put(
289
+ I("Uploaded cover (thumbnail) of pack {}").format(self.pack_short_name)
290
+ )
291
+ return True
292
+ except TelegramError as e:
293
+ self.cb.put(
294
+ I("Cannot upload cover (thumbnail) of pack {}. Reason: {}").format(
295
+ self.pack_short_name, e
296
+ )
297
+ )
298
+ return False
299
+
300
+ async def get_pack_url(self) -> str:
301
+ if self.is_emoji:
302
+ return f"https://t.me/addemoji/{self.pack_short_name}"
303
+ else:
304
+ return f"https://t.me/addstickers/{self.pack_short_name}"
305
+
306
+ async def _download_sticker(
307
+ self,
308
+ sticker: Union[PhotoSize, Sticker],
309
+ f_id: str,
310
+ out_dir: Path,
311
+ results: Dict[str, bool],
312
+ emoji_dict: Dict[str, str],
313
+ ) -> None:
314
+ try:
315
+ sticker_file = await sticker.get_file(
316
+ read_timeout=self.timeout,
317
+ write_timeout=self.timeout,
318
+ connect_timeout=self.timeout,
319
+ pool_timeout=self.timeout,
320
+ )
321
+ except TelegramError as e:
322
+ self.cb.put(I("Failed to download {}: {}").format(f_id, str(e)))
323
+ results[f_id] = False
324
+ return
325
+ fpath = sticker_file.file_path
326
+ assert fpath is not None
327
+ ext = Path(fpath).suffix
328
+ f_name = f_id + ext
329
+ f_path = Path(out_dir, f_name)
330
+ await sticker_file.download_to_drive(
331
+ custom_path=f_path,
332
+ read_timeout=self.timeout,
333
+ write_timeout=self.timeout,
334
+ connect_timeout=self.timeout,
335
+ pool_timeout=self.timeout,
336
+ )
337
+ if isinstance(sticker, Sticker) and sticker.emoji is not None:
338
+ emoji_dict[f_id] = sticker.emoji
339
+ self.cb.put(I("Downloaded {}").format(f_name))
340
+ results[f_id] = True
341
+ if f_id != "cover":
342
+ self.cb.put("update_bar")
343
+
344
+ async def pack_dl(
345
+ self, pack_short_name: str, out_dir: Path
346
+ ) -> Tuple[Dict[str, bool], Dict[str, str]]:
347
+ results: Dict[str, bool] = {}
348
+ emoji_dict: Dict[str, str] = {}
349
+
350
+ try:
351
+ sticker_set: TGStickerSet = await self.application.bot.get_sticker_set(
352
+ pack_short_name,
353
+ read_timeout=self.timeout,
354
+ write_timeout=self.timeout,
355
+ connect_timeout=self.timeout,
356
+ pool_timeout=self.timeout,
357
+ )
358
+ except TelegramError as e:
359
+ self.cb.put(
360
+ I("Failed to download telegram sticker set {} due to: {}").format(
361
+ pack_short_name, e
362
+ )
363
+ )
364
+ return results, emoji_dict
365
+
366
+ self.cb.put(
367
+ (
368
+ "bar",
369
+ None,
370
+ {
371
+ "set_progress_mode": "determinate",
372
+ "steps": len(sticker_set.stickers),
373
+ },
374
+ )
375
+ )
376
+
377
+ async with anyio.create_task_group() as tg:
378
+ for num, sticker in enumerate(sticker_set.stickers):
379
+ f_id = str(num).zfill(3)
380
+ tg.start_soon(
381
+ self._download_sticker, sticker, f_id, out_dir, results, emoji_dict
382
+ )
383
+
384
+ if sticker_set.thumbnail is not None:
385
+ results_thumb: Dict[str, bool] = {}
386
+ tg.start_soon(
387
+ self._download_sticker,
388
+ sticker_set.thumbnail,
389
+ "cover",
390
+ out_dir,
391
+ results_thumb,
392
+ emoji_dict,
393
+ )
394
+
395
+ return results, emoji_dict
396
+
397
+
398
+ class TelethonAPI(TelegramAPI):
399
+ def __init__(self, *args: Any, **kwargs: Any):
400
+ super().__init__(*args, **kwargs)
401
+
402
+ async def setup(
403
+ self,
404
+ opt_cred: CredOption,
405
+ is_upload: bool,
406
+ cb: CallbackProtocol,
407
+ cb_return: CallbackReturn,
408
+ ) -> bool:
409
+ self.opt_cred = opt_cred
410
+ self.cb = cb
411
+ self.cb_return = cb_return
412
+
413
+ success, client, _, _, _ = await AuthTelethon(
414
+ self.opt_cred, self.cb
415
+ ).start_async()
416
+
417
+ if success is True and client is not None:
418
+ self.client = client
419
+ repl = await self._send_and_recv("/start")
420
+ if "Sticker Bot" not in repl:
421
+ return False
422
+ return success
423
+
424
+ async def exit(self) -> None:
425
+ self.client.disconnect()
426
+
427
+ async def set_upload_pack_short_name(self, pack_title: str) -> str:
428
+ self.pack_title = pack_title
429
+ self.pack_short_name = re.sub(
430
+ "[^0-9a-zA-Z]+", "_", pack_title
431
+ ) # name used in url, only alphanum and underscore only
432
+ return self.pack_short_name
433
+
434
+ async def set_upload_pack_type(self, is_emoji: bool) -> None:
435
+ self.is_emoji = is_emoji
436
+
437
+ async def check_pack_exist(self) -> bool:
438
+ try:
439
+ await self.client(
440
+ messages.GetStickerSetRequest(
441
+ InputStickerSetShortName(self.pack_short_name), 0
442
+ )
443
+ )
444
+ except StickersetInvalidError:
445
+ return False
446
+
447
+ return True
448
+
449
+ async def _send_and_recv(self, msg: Union[str, Path]) -> str:
450
+ if isinstance(msg, str):
451
+ sent_message = await self.client.send_message("Stickers", msg)
452
+ else:
453
+ sent_message = cast(
454
+ Message,
455
+ await self.client.send_file("Stickers", msg, force_document=True), # type: ignore
456
+ )
457
+
458
+ for _ in range(5):
459
+ # https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
460
+ # In a single chat, avoid sending more than one message per second.
461
+ time.sleep(1)
462
+ last_message = cast(
463
+ List[Message],
464
+ await self.client.get_messages("Stickers", 1), # type: ignore
465
+ )[0]
466
+ if sent_message.id != last_message.id:
467
+ return last_message.message
468
+
469
+ return "timeout"
470
+
471
+ async def pack_del(self) -> bool:
472
+ if self.is_emoji:
473
+ repl = await self._send_and_recv("/delemoji")
474
+ else:
475
+ repl = await self._send_and_recv("/delpack")
476
+ if repl != "Choose the sticker set you want to delete.":
477
+ self.cb.put(self.MSG_FAIL_DEL.format(self.pack_short_name, repl))
478
+ return False
479
+ repl = await self._send_and_recv(self.pack_short_name)
480
+ if "Yes, I am totally sure." not in repl:
481
+ self.cb.put(self.MSG_FAIL_DEL.format(self.pack_short_name, repl))
482
+ return False
483
+ repl = await self._send_and_recv("Yes, I am totally sure.")
484
+ if "Done!" not in repl:
485
+ self.cb.put(self.MSG_FAIL_DEL.format(self.pack_short_name, repl))
486
+ return False
487
+
488
+ return True
489
+
490
+ async def pack_new(
491
+ self, stickers_list: List[TelegramSticker], sticker_type: str
492
+ ) -> Tuple[int, int]:
493
+ stickers_ok = 0
494
+ if self.is_emoji:
495
+ repl = await self._send_and_recv("/newemojipack")
496
+ elif stickers_list[0][3] == "static":
497
+ repl = await self._send_and_recv("/newpack")
498
+ elif stickers_list[0][3] == "video":
499
+ repl = await self._send_and_recv("/newvideo")
500
+ elif stickers_list[0][3] == "animated":
501
+ repl = await self._send_and_recv("/newanimated")
502
+ else:
503
+ self.cb.put(
504
+ I(
505
+ "Cannot upload any sticker to {} due to invalid sticker format {}"
506
+ ).format(self.pack_short_name, stickers_list[0][3])
507
+ )
508
+ return len(stickers_list), 0
509
+ if "Yay!" not in repl:
510
+ self.cb.put(self.MSG_FAIL_ALL.format(repl))
511
+ return len(stickers_list), 0
512
+
513
+ if self.is_emoji:
514
+ repl = await self._send_and_recv(
515
+ f"{stickers_list[0][3].capitalize()} emoji"
516
+ )
517
+ if "Yay!" not in repl:
518
+ self.cb.put(self.MSG_FAIL_ALL.format(repl))
519
+ return len(stickers_list), 0
520
+
521
+ repl = await self._send_and_recv(self.pack_title)
522
+ if "Alright!" not in repl:
523
+ self.cb.put(self.MSG_FAIL_ALL.format(repl))
524
+ return len(stickers_list), 0
525
+ self.cb.put(
526
+ (
527
+ "bar",
528
+ None,
529
+ {
530
+ "set_progress_mode": "determinate",
531
+ "steps": len(stickers_list),
532
+ },
533
+ )
534
+ )
535
+ for i in stickers_list:
536
+ repl = await self._send_and_recv(i[0])
537
+ if "Thanks!" not in repl:
538
+ self.cb.put(
539
+ self.MSG_FAIL_STICKER.format(
540
+ sticker=i[0], pack=self.pack_short_name, reason=repl
541
+ )
542
+ )
543
+ self.cb.put("update_bar")
544
+ continue
545
+ repl = await self._send_and_recv("".join(i[2]))
546
+ if "Congratulations." not in repl:
547
+ self.cb.put(
548
+ self.MSG_FAIL_STICKER.format(
549
+ sticker=i[0], pack=self.pack_short_name, reason=repl
550
+ )
551
+ )
552
+ self.cb.put("update_bar")
553
+ continue
554
+ stickers_ok += 1
555
+ self.cb.put("update_bar")
556
+ repl = await self._send_and_recv("/publish")
557
+ if "icon" not in repl:
558
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
559
+ return len(stickers_list), 0
560
+ repl = await self._send_and_recv("/skip")
561
+ if "Please provide a short name" not in repl:
562
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
563
+ return len(stickers_list), 0
564
+ repl = await self._send_and_recv(self.pack_short_name)
565
+ if "Kaboom!" not in repl:
566
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
567
+ return len(stickers_list), 0
568
+
569
+ self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
570
+
571
+ return len(stickers_list), stickers_ok
572
+
573
+ async def pack_add(
574
+ self, stickers_list: List[TelegramSticker], sticker_type: str
575
+ ) -> Tuple[int, int]:
576
+ stickers_ok = 0
577
+ if self.is_emoji:
578
+ repl = await self._send_and_recv("/addemoji")
579
+ else:
580
+ repl = await self._send_and_recv("/addsticker")
581
+ if "Choose" not in repl:
582
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
583
+ return len(stickers_list), 0
584
+ repl = await self._send_and_recv(self.pack_short_name)
585
+ if "Alright!" not in repl:
586
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
587
+ return len(stickers_list), 0
588
+
589
+ self.cb.put(
590
+ (
591
+ "bar",
592
+ None,
593
+ {
594
+ "set_progress_mode": "determinate",
595
+ "steps": len(stickers_list),
596
+ },
597
+ )
598
+ )
599
+ for i in stickers_list:
600
+ repl = await self._send_and_recv(i[0])
601
+ if "Thanks!" not in repl:
602
+ self.cb.put(
603
+ self.MSG_FAIL_STICKER.format(
604
+ sticker=i[0], pack=self.pack_short_name, reason=repl
605
+ )
606
+ )
607
+ self.cb.put("update_bar")
608
+ continue
609
+ repl = await self._send_and_recv("".join(i[2]))
610
+ if "There we go." not in repl:
611
+ self.cb.put(
612
+ self.MSG_FAIL_STICKER.format(
613
+ sticker=i[0], pack=self.pack_short_name, reason=repl
614
+ )
615
+ )
616
+ self.cb.put("update_bar")
617
+ continue
618
+ self.cb.put("update_bar")
619
+ stickers_ok += 1
620
+
621
+ self.cb.put(("bar", None, {"set_progress_mode": "indeterminate"}))
622
+
623
+ repl = await self._send_and_recv("/done")
624
+ if "OK" not in repl:
625
+ self.cb.put(self.MSG_FAIL_PACK.format(self.pack_short_name, repl))
626
+ return len(stickers_list), 0
627
+
628
+ return len(stickers_list), stickers_ok
629
+
630
+ async def pack_thumbnail(self, thumbnail: TelegramSticker) -> bool:
631
+ repl = await self._send_and_recv("/setpackicon")
632
+ if "OK" not in repl:
633
+ self.cb.put(self.MSG_FAIL_PACK_ICON.format(self.pack_short_name, repl))
634
+ return False
635
+ repl = await self._send_and_recv(thumbnail[0])
636
+ if "Enjoy!" not in repl:
637
+ self.cb.put(self.MSG_FAIL_PACK_ICON.format(self.pack_short_name, repl))
638
+ return False
639
+ return True
640
+
641
+ async def get_pack_url(self) -> str:
642
+ if self.is_emoji:
643
+ return f"https://t.me/addemoji/{self.pack_short_name}"
644
+ else:
645
+ return f"https://t.me/addstickers/{self.pack_short_name}"
646
+
647
+ async def _download_sticker(
648
+ self,
649
+ sticker: TypeDocument,
650
+ f_id: str,
651
+ out_dir: Path,
652
+ id_to_emoji: Dict[int, str],
653
+ emoji_dict: Dict[str, str],
654
+ results: Dict[str, bool],
655
+ ) -> None:
656
+ fpath_attr = [
657
+ attr
658
+ for attr in sticker.attributes # type: ignore
659
+ if isinstance(attr, DocumentAttributeFilename)
660
+ ]
661
+ assert len(fpath_attr) > 0
662
+ fpath = fpath_attr[0].file_name
663
+ ext = Path(fpath).suffix
664
+ f_name = f_id + ext
665
+ f_path = Path(out_dir, f_name)
666
+
667
+ try:
668
+ await self.client.download_media(sticker, file=f_path) # type: ignore
669
+ except Exception as e:
670
+ self.cb.put(I("Failed to download {}: {}").format(f_id, str(e)))
671
+ results[f_id] = False
672
+ return
673
+
674
+ emoji_dict[f_id] = id_to_emoji[sticker.id]
675
+ self.cb.put(I("Downloaded {}").format(f_name))
676
+ results[f_id] = True
677
+ self.cb.put("update_bar")
678
+
679
+ async def pack_dl(
680
+ self, pack_short_name: str, out_dir: Path
681
+ ) -> Tuple[Dict[str, bool], Dict[str, str]]:
682
+ results: Dict[str, bool] = {}
683
+ emoji_dict: Dict[str, str] = {}
684
+ id_to_emoji: Dict[int, str] = defaultdict(str)
685
+
686
+ sticker_set = cast(
687
+ TLStickerSet,
688
+ await self.client(
689
+ messages.GetStickerSetRequest(
690
+ InputStickerSetShortName(pack_short_name), 0
691
+ )
692
+ ),
693
+ )
694
+
695
+ self.cb.put(
696
+ (
697
+ "bar",
698
+ None,
699
+ {
700
+ "set_progress_mode": "determinate",
701
+ "steps": len(sticker_set.documents),
702
+ },
703
+ )
704
+ )
705
+
706
+ for pack in sticker_set.packs:
707
+ for document_id in pack.documents:
708
+ id_to_emoji[document_id] += pack.emoticon
709
+
710
+ ext = ""
711
+ async with anyio.create_task_group() as tg:
712
+ for num, sticker in enumerate(sticker_set.documents):
713
+ f_id = str(num).zfill(3)
714
+ tg.start_soon(
715
+ self._download_sticker,
716
+ sticker,
717
+ f_id,
718
+ out_dir,
719
+ id_to_emoji,
720
+ emoji_dict,
721
+ results,
722
+ )
723
+
724
+ if sticker_set.set.thumb_version and ext:
725
+ try:
726
+ await self.client.download_file( # type: ignore
727
+ InputStickerSetThumb(
728
+ InputStickerSetShortName(pack_short_name),
729
+ thumb_version=sticker_set.set.thumb_version,
730
+ ),
731
+ f"cover{ext}",
732
+ )
733
+ except Exception as e:
734
+ self.cb.put(I("Failed to download cover{}: {}").format(ext, str(e)))
735
+
736
+ return results, emoji_dict