sticker-convert 2.8.12__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 (124) hide show
  1. sticker_convert/__main__.py +24 -24
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/auth/auth_discord.py +149 -0
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/auth/auth_signal.py +139 -0
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/auth/auth_viber.py +250 -0
  12. sticker_convert/auth/telegram_api.py +736 -0
  13. sticker_convert/cli.py +623 -509
  14. sticker_convert/converter.py +1093 -962
  15. sticker_convert/definitions.py +11 -0
  16. sticker_convert/downloaders/download_band.py +111 -0
  17. sticker_convert/downloaders/download_base.py +171 -130
  18. sticker_convert/downloaders/download_discord.py +92 -0
  19. sticker_convert/downloaders/download_kakao.py +417 -255
  20. sticker_convert/downloaders/download_line.py +484 -472
  21. sticker_convert/downloaders/download_ogq.py +80 -0
  22. sticker_convert/downloaders/download_signal.py +108 -92
  23. sticker_convert/downloaders/download_telegram.py +56 -130
  24. sticker_convert/downloaders/download_viber.py +121 -95
  25. sticker_convert/gui.py +788 -795
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -165
  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 -162
  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 -715
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
  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 -135
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  42. sticker_convert/ios-message-stickers-template/README.md +10 -10
  43. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  50. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  51. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  52. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  53. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  54. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  55. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  56. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  57. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  58. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  59. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  60. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  61. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  62. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  63. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  64. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  65. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  66. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  67. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  68. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  69. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  70. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  71. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  72. sticker_convert/job.py +279 -179
  73. sticker_convert/job_option.py +15 -2
  74. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  75. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  76. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  77. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  78. sticker_convert/py.typed +0 -0
  79. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  80. sticker_convert/resources/compression.json +220 -16
  81. sticker_convert/resources/emoji.json +527 -77
  82. sticker_convert/resources/help.ja_JP.json +88 -0
  83. sticker_convert/resources/help.json +24 -10
  84. sticker_convert/resources/help.zh_CN.json +88 -0
  85. sticker_convert/resources/help.zh_TW.json +88 -0
  86. sticker_convert/resources/input.ja_JP.json +74 -0
  87. sticker_convert/resources/input.json +121 -71
  88. sticker_convert/resources/input.zh_CN.json +74 -0
  89. sticker_convert/resources/input.zh_TW.json +74 -0
  90. sticker_convert/resources/memdump_linux.sh +25 -0
  91. sticker_convert/resources/memdump_windows.ps1 +8 -0
  92. sticker_convert/resources/output.ja_JP.json +38 -0
  93. sticker_convert/resources/output.json +24 -0
  94. sticker_convert/resources/output.zh_CN.json +38 -0
  95. sticker_convert/resources/output.zh_TW.json +38 -0
  96. sticker_convert/uploaders/compress_wastickers.py +186 -156
  97. sticker_convert/uploaders/upload_base.py +44 -35
  98. sticker_convert/uploaders/upload_signal.py +218 -173
  99. sticker_convert/uploaders/upload_telegram.py +353 -388
  100. sticker_convert/uploaders/upload_viber.py +178 -0
  101. sticker_convert/uploaders/xcode_imessage.py +295 -285
  102. sticker_convert/utils/callback.py +238 -6
  103. sticker_convert/utils/chrome_remotedebug.py +219 -0
  104. sticker_convert/utils/chromiums/linux.py +52 -0
  105. sticker_convert/utils/chromiums/osx.py +68 -0
  106. sticker_convert/utils/chromiums/windows.py +45 -0
  107. sticker_convert/utils/emoji.py +28 -0
  108. sticker_convert/utils/files/json_resources_loader.py +24 -19
  109. sticker_convert/utils/files/metadata_handler.py +8 -7
  110. sticker_convert/utils/files/run_bin.py +1 -1
  111. sticker_convert/utils/media/codec_info.py +99 -67
  112. sticker_convert/utils/media/format_verify.py +33 -20
  113. sticker_convert/utils/process.py +231 -0
  114. sticker_convert/utils/translate.py +108 -0
  115. sticker_convert/utils/url_detect.py +40 -33
  116. sticker_convert/version.py +1 -1
  117. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
  118. sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
  119. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
  120. sticker_convert/utils/auth/get_signal_auth.py +0 -129
  121. sticker_convert-2.8.12.dist-info/RECORD +0 -101
  122. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  123. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
  124. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +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, 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