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
@@ -1,300 +1,331 @@
1
- #!/usr/bin/env python3
2
- import json
3
- import secrets
4
- import uuid
5
- from typing import Any, Callable, Dict, Optional
6
- from urllib.parse import parse_qs, urlparse
7
-
8
- import requests
9
-
10
- from sticker_convert.job_option import CredOption
11
-
12
-
13
- class GetKakaoAuth:
14
- def __init__(
15
- self,
16
- opt_cred: CredOption,
17
- cb_msg: Callable[..., None] = print,
18
- cb_msg_block: Callable[..., Any] = input,
19
- cb_ask_str: Callable[..., str] = input,
20
- ) -> None:
21
- self.username = opt_cred.kakao_username
22
- self.password = opt_cred.kakao_password
23
- self.country_code = opt_cred.kakao_country_code
24
- self.phone_number = opt_cred.kakao_phone_number
25
-
26
- self.cb_msg = cb_msg
27
- self.cb_msg_block = cb_msg_block
28
- self.cb_ask_str = cb_ask_str
29
-
30
- self.device_uuid = secrets.token_hex(32)
31
- self.device_ssaid = secrets.token_hex(20)
32
- self.uuid_c = str(uuid.uuid4())
33
- self.device_info = (
34
- f"android/30; uuid={self.device_uuid}; ssaid={self.device_ssaid}; "
35
- + 'model=ANDROID-SDK-BUILT-FOR-X86; screen_resolution=1080x1920; sim=310260/1/us; onestore=false; uvc2={"volume":5,"network_operator":"310260","is_roaming":"false","va":[],"brightness":102,"totalMemory":30866040,"batteryPct":1,"webviewVersion":"83.0.4103.106"}'
36
- )
37
- self.app_platform = "android"
38
- self.app_version_number = "10.0.3"
39
- self.app_language = "en"
40
- self.app_version = (
41
- f"{self.app_platform}/{self.app_version_number}/{self.app_language}"
42
- )
43
-
44
- self.headers = {
45
- "Host": "katalk.kakao.com",
46
- "Accept-Language": "en",
47
- "User-Agent": "KT/10.0.3 An/11 en",
48
- "Device-Info": self.device_info,
49
- "A": self.app_version,
50
- "C": self.uuid_c,
51
- "Content-Type": "application/json",
52
- "Connection": "close",
53
- }
54
-
55
- def login(self) -> bool:
56
- self.cb_msg("Logging in")
57
-
58
- json_data = {
59
- "id": self.username,
60
- "password": self.password,
61
- }
62
-
63
- response = requests.post(
64
- "https://katalk.kakao.com/android/account2/login",
65
- headers=self.headers,
66
- json=json_data,
67
- )
68
-
69
- response_json = json.loads(response.text)
70
-
71
- if response_json["status"] != 0:
72
- self.cb_msg_block(f"Failed at login: {response.text}")
73
- return False
74
-
75
- self.headers["Ss"] = response.headers["Set-SS"]
76
- self.country_dicts = response_json["viewData"]["countries"]["all"]
77
-
78
- return True
79
-
80
- def get_country_iso(self) -> bool:
81
- self.country_iso = None
82
- for country_dict in self.country_dicts:
83
- if country_dict["code"] == self.country_code:
84
- self.country_iso = country_dict["iso"]
85
-
86
- if not self.country_iso:
87
- self.cb_msg_block("Invalid country code")
88
- return False
89
-
90
- return True
91
-
92
- def enter_phone(self) -> bool:
93
- self.cb_msg("Submitting phone number")
94
-
95
- json_data: Dict[str, Any] = {
96
- "countryCode": self.country_code,
97
- "countryIso": self.country_iso,
98
- "phoneNumber": self.phone_number,
99
- "method": "sms",
100
- "termCodes": [],
101
- "simPhoneNumber": f"+{self.country_code}{self.phone_number}",
102
- }
103
-
104
- response = requests.post(
105
- "https://katalk.kakao.com/android/account2/phone-number",
106
- headers=self.headers,
107
- json=json_data,
108
- )
109
-
110
- response_json = json.loads(response.text)
111
-
112
- if response_json["status"] != 0:
113
- self.cb_msg_block(f"Failed at entering phone number: {response.text}")
114
- return False
115
-
116
- self.verify_method = response_json["view"]
117
-
118
- if self.verify_method == "passcode":
119
- return self.verify_receive_sms()
120
- if self.verify_method == "mo-send":
121
- dest_number = response_json["viewData"]["moNumber"]
122
- msg = response_json["viewData"]["moMessage"]
123
- return self.verify_send_sms(dest_number, msg)
124
- self.cb_msg_block(f"Unknown verification method: {response.text}")
125
- return False
126
-
127
- def verify_send_sms(self, dest_number: str, msg: str) -> bool:
128
- self.cb_msg("Verification by sending SMS")
129
-
130
- response = requests.post(
131
- "https://katalk.kakao.com/android/account2/mo-sent", headers=self.headers
132
- )
133
-
134
- response_json = json.loads(response.text)
135
-
136
- if response_json["status"] != 0:
137
- self.cb_msg_block(f"Failed at confirm sending SMS: {response.text}")
138
- return False
139
-
140
- prompt = f"Send this SMS message to number {dest_number} then press enter:"
141
- self.cb_msg(msg)
142
- if self.cb_ask_str != input:
143
- self.cb_ask_str(prompt, initialvalue=msg, cli_show_initialvalue=False)
144
- else:
145
- input(prompt)
146
-
147
- response = requests.post(
148
- "https://katalk.kakao.com/android/account2/mo-confirm", headers=self.headers
149
- )
150
-
151
- response_json = json.loads(response.text)
152
-
153
- if response_json["status"] != 0:
154
- self.cb_msg_block(f"Failed at verifying SMS sent: {response.text}")
155
- return False
156
-
157
- if response_json.get("reason") or "error" in response_json.get("message", ""):
158
- self.cb_msg_block(f"Failed at verifying SMS sent: {response.text}")
159
- return False
160
-
161
- self.confirm_url = response_json.get("viewData", {}).get("url")
162
-
163
- return True
164
-
165
- def verify_receive_sms(self) -> bool:
166
- self.cb_msg("Verification by receiving SMS")
167
-
168
- passcode = self.cb_ask_str("Enter passcode received from SMS:")
169
-
170
- json_data = {
171
- "passcode": passcode,
172
- }
173
-
174
- response = requests.post(
175
- "https://katalk.kakao.com/android/account2/passcode",
176
- headers=self.headers,
177
- json=json_data,
178
- )
179
-
180
- response_json = json.loads(response.text)
181
-
182
- if response_json["status"] != 0:
183
- self.cb_msg_block(f"Failed at verifying passcode: {response.text}")
184
- return False
185
-
186
- self.confirm_url = response_json.get("viewData", {}).get("url")
187
-
188
- return True
189
-
190
- def confirm_device_change(self) -> bool:
191
- self.cb_msg("Confirm device change")
192
-
193
- confirm_url_parsed = urlparse(self.confirm_url) # type: ignore
194
- confirm_url_qs = parse_qs(confirm_url_parsed.query) # type: ignore
195
- session_token: str = confirm_url_qs["sessionToken"][0] # type: ignore
196
-
197
- headers_browser = {
198
- "Host": "katalk.kakao.com",
199
- "Accept": "*/*",
200
- "X-Requested-With": "XMLHttpRequest",
201
- "User-Agent": "Mozilla/5.0 (Linux; Android 11; Android SDK built for x86 Build/RSR1.210210.001.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36;KAKAOTALK 2410030",
202
- "Content-Type": "application/json",
203
- "Origin": "https://katalk.kakao.com",
204
- "Sec-Fetch-Site": "same-origin",
205
- "Sec-Fetch-Mode": "cors",
206
- "Sec-Fetch-Dest": "empty",
207
- "Referer": self.confirm_url,
208
- "Accept-Language": "en-US,en;q=0.9",
209
- "Connection": "close",
210
- }
211
-
212
- json_data: Dict[str, str] = {
213
- "decision": "continue",
214
- "lang": self.app_language,
215
- "sessionToken": session_token,
216
- "appVersion": self.app_version_number,
217
- }
218
-
219
- response = requests.post(
220
- "https://katalk.kakao.com/android/account2/confirm-device-change",
221
- headers=headers_browser,
222
- json=json_data,
223
- )
224
-
225
- response_json = json.loads(response.text)
226
-
227
- if response_json["status"] != 0:
228
- self.cb_msg_block(f"Failed at confirm device change: {response.text}")
229
- return False
230
-
231
- return True
232
-
233
- def passcode_callback(self) -> bool:
234
- self.cb_msg("Passcode callback")
235
-
236
- response = requests.get(
237
- "https://katalk.kakao.com/android/account2/passcode/callback",
238
- headers=self.headers,
239
- )
240
-
241
- response_json = json.loads(response.text)
242
-
243
- if response_json["status"] != 0:
244
- self.cb_msg_block(f"Failed at passcode callback: {response.text}")
245
- return False
246
-
247
- self.nickname = response_json.get("viewData", {}).get("nickname")
248
-
249
- if self.nickname is None:
250
- self.cb_msg_block(f"Failed at passcode callback: {response.text}")
251
- return False
252
-
253
- return True
254
-
255
- def get_profile(self) -> bool:
256
- self.cb_msg("Get profile")
257
-
258
- json_data = {
259
- "nickname": self.nickname,
260
- "profileImageFlag": 1,
261
- "friendAutomation": True,
262
- }
263
-
264
- response = requests.post(
265
- "https://katalk.kakao.com/android/account2/profile",
266
- headers=self.headers,
267
- json=json_data,
268
- )
269
-
270
- response_json = json.loads(response.text)
271
-
272
- if response_json["status"] != 0:
273
- self.cb_msg_block(f"Failed at get profile: {response.text}")
274
- return False
275
-
276
- self.access_token = response_json["signupData"]["oauth2Token"]["accessToken"]
277
-
278
- return True
279
-
280
- def get_cred(self) -> Optional[str]:
281
- self.cb_msg("Get authorization token")
282
-
283
- authorization_token = None
284
-
285
- steps = (
286
- self.login,
287
- self.get_country_iso,
288
- self.enter_phone,
289
- self.confirm_device_change,
290
- self.passcode_callback,
291
- self.get_profile,
292
- )
293
-
294
- for step in steps:
295
- success = step()
296
- if not success:
297
- return None
298
-
299
- authorization_token = self.access_token + "-" + self.device_uuid
300
- return authorization_token
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import secrets
4
+ import uuid
5
+ from typing import Any, Dict, Optional, Tuple
6
+ from urllib.parse import parse_qs, urlparse
7
+
8
+ import requests
9
+ from bs4 import BeautifulSoup
10
+
11
+ from sticker_convert.auth.auth_base import AuthBase
12
+ from sticker_convert.utils.translate import I
13
+
14
+
15
+ class AuthKakaoAndroidLogin(AuthBase):
16
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
17
+ self.OK_MSG = I("Got auth_token successfully:\nauth_token={}\n")
18
+
19
+ super().__init__(*args, **kwargs)
20
+ self.username = self.opt_cred.kakao_username
21
+ self.password = self.opt_cred.kakao_password
22
+ self.country_code = self.opt_cred.kakao_country_code
23
+ self.phone_number = self.opt_cred.kakao_phone_number
24
+
25
+ self.device_uuid = secrets.token_hex(32)
26
+ self.device_ssaid = secrets.token_hex(20)
27
+ self.uuid_c = str(uuid.uuid4())
28
+ self.device_info = (
29
+ f"android/30; uuid={self.device_uuid}; ssaid={self.device_ssaid}; "
30
+ + "model=SDK_GPHONE_X86_64; screen_resolution=1080x1920; sim=310260/1/us; onestore=false; uvc3=null"
31
+ )
32
+ self.app_platform = "android"
33
+ self.app_version_number = self.get_version()
34
+ self.app_language = "en"
35
+ self.app_version = (
36
+ f"{self.app_platform}/{self.app_version_number}/{self.app_language}"
37
+ )
38
+
39
+ self.headers = {
40
+ "Host": "katalk.kakao.com",
41
+ "Accept-Language": "en",
42
+ "User-Agent": f"KT/{self.app_version_number} An/11 en",
43
+ "Device-Info": self.device_info,
44
+ "A": self.app_version,
45
+ "C": self.uuid_c,
46
+ "Content-Type": "application/json",
47
+ "Connection": "close",
48
+ }
49
+
50
+ def get_version(self) -> str:
51
+ # It is difficult to get app version number from Google Play
52
+ r = requests.get(
53
+ "https://apkpure.net/kakaotalk-messenger/com.kakao.talk/versions"
54
+ )
55
+ soup = BeautifulSoup(r.text, "html.parser")
56
+ for li in soup.find_all("li"):
57
+ a = li.find("a", class_="dt-version-icon")
58
+ if a is None:
59
+ continue
60
+ if "/kakaotalk-messenger/com.kakao.talk/download/" in a.get("href", ""):
61
+ return a.get("href").split("/")[-1]
62
+ return "25.2.1"
63
+
64
+ def login(self) -> Tuple[bool, str]:
65
+ msg = I("Getting Kakao authorization token by Android login: Logging in")
66
+ self.cb.put(("msg_dynamic", (msg,), None))
67
+
68
+ json_data = {
69
+ "id": self.username,
70
+ "password": self.password,
71
+ }
72
+
73
+ response = requests.post(
74
+ "https://katalk.kakao.com/android/account2/login",
75
+ headers=self.headers,
76
+ json=json_data,
77
+ )
78
+
79
+ response_json = json.loads(response.text)
80
+
81
+ self.cb.put(("msg_dynamic", (None,), None))
82
+ if response_json["status"] != 0:
83
+ return False, f"Failed at login: {response.text}"
84
+
85
+ self.headers["Ss"] = response.headers["Set-SS"]
86
+ self.country_dicts = response_json["viewData"]["countries"]["all"]
87
+
88
+ return True, ""
89
+
90
+ def get_country_iso(self) -> Tuple[bool, str]:
91
+ self.country_iso = None
92
+ for country_dict in self.country_dicts:
93
+ if country_dict["code"] == self.country_code:
94
+ self.country_iso = country_dict["iso"]
95
+
96
+ if not self.country_iso:
97
+ return False, "Invalid country code"
98
+
99
+ return True, ""
100
+
101
+ def enter_phone(self) -> Tuple[bool, str]:
102
+ msg = I(
103
+ "Getting Kakao authorization token by Android login: Submitting phone number"
104
+ )
105
+ self.cb.put(("msg_dynamic", (msg,), None))
106
+
107
+ json_data: Dict[str, Any] = {
108
+ "countryCode": self.country_code,
109
+ "countryIso": self.country_iso,
110
+ "phoneNumber": self.phone_number,
111
+ "termCodes": [],
112
+ "simPhoneNumber": f"+{self.country_code}{self.phone_number}",
113
+ }
114
+
115
+ response = requests.post(
116
+ "https://katalk.kakao.com/android/account2/phone-number",
117
+ headers=self.headers,
118
+ json=json_data,
119
+ )
120
+
121
+ response_json = json.loads(response.text)
122
+
123
+ self.cb.put(("msg_dynamic", (None,), None))
124
+ if response_json["status"] != 0:
125
+ return False, f"Failed at entering phone number: {response.text}"
126
+
127
+ self.verify_method = response_json["view"]
128
+
129
+ if self.verify_method == "passcode":
130
+ return self.verify_receive_sms()
131
+ if self.verify_method == "mo-send":
132
+ dest_number = response_json["viewData"]["moNumber"]
133
+ msg = response_json["viewData"]["moMessage"]
134
+ return self.verify_send_sms(dest_number, msg)
135
+ return False, f"Unknown verification method: {response.text}"
136
+
137
+ def verify_send_sms(self, dest_number: str, verify_msg: str) -> Tuple[bool, str]:
138
+ response = requests.post(
139
+ "https://katalk.kakao.com/android/account2/mo-sent", headers=self.headers
140
+ )
141
+
142
+ response_json = json.loads(response.text)
143
+
144
+ if response_json["status"] != 0:
145
+ return False, f"Failed at confirm sending SMS: {response.text}"
146
+
147
+ prompt = I("Send this SMS message to number {} then press enter:").format(
148
+ dest_number
149
+ )
150
+ self.cb.put(
151
+ (
152
+ "ask_str",
153
+ None,
154
+ {
155
+ "msg": prompt,
156
+ "initialvalue": verify_msg,
157
+ "cli_show_initialvalue": False,
158
+ },
159
+ )
160
+ )
161
+
162
+ response = requests.post(
163
+ "https://katalk.kakao.com/android/account2/mo-confirm", headers=self.headers
164
+ )
165
+
166
+ response_json = json.loads(response.text)
167
+
168
+ if response_json["status"] != 0:
169
+ return False, f"Failed at verifying SMS sent: {response.text}"
170
+
171
+ if response_json.get("reason") or "error" in response_json.get("message", ""):
172
+ return False, f"Failed at verifying SMS sent: {response.text}"
173
+
174
+ self.confirm_url = response_json.get("viewData", {}).get("url")
175
+
176
+ return True, ""
177
+
178
+ def verify_receive_sms(self) -> Tuple[bool, str]:
179
+ msg = I("Enter passcode received from SMS:")
180
+ passcode = self.cb.put(("ask_str", (msg,), None))
181
+
182
+ json_data = {
183
+ "passcode": passcode,
184
+ }
185
+
186
+ response = requests.post(
187
+ "https://katalk.kakao.com/android/account2/passcode",
188
+ headers=self.headers,
189
+ json=json_data,
190
+ )
191
+
192
+ response_json = json.loads(response.text)
193
+
194
+ self.cb.put(("msg_dynamic", (None,), None))
195
+ if response_json["status"] != 0:
196
+ return False, f"Failed at verifying passcode: {response.text}"
197
+
198
+ self.confirm_url = response_json.get("viewData", {}).get("url")
199
+
200
+ return True, ""
201
+
202
+ def confirm_device_change(self) -> Tuple[bool, str]:
203
+ msg = I(
204
+ "Getting Kakao authorization token by Android login: Confirm device change"
205
+ )
206
+ self.cb.put(("msg_dynamic", (msg,), None))
207
+
208
+ confirm_url_parsed = urlparse(self.confirm_url) # type: ignore
209
+ confirm_url_qs = parse_qs(confirm_url_parsed.query) # type: ignore
210
+ session_token: str = confirm_url_qs["sessionToken"][0] # type: ignore
211
+
212
+ headers_browser = {
213
+ "Host": "katalk.kakao.com",
214
+ "Accept": "*/*",
215
+ "X-Requested-With": "XMLHttpRequest",
216
+ "User-Agent": "Mozilla/5.0 (Linux; Android 11; Android SDK built for x86 Build/RSR1.210210.001.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36;KAKAOTALK 2410030",
217
+ "Content-Type": "application/json",
218
+ "Origin": "https://katalk.kakao.com",
219
+ "Sec-Fetch-Site": "same-origin",
220
+ "Sec-Fetch-Mode": "cors",
221
+ "Sec-Fetch-Dest": "empty",
222
+ "Referer": self.confirm_url,
223
+ "Accept-Language": "en-US,en;q=0.9",
224
+ "Connection": "close",
225
+ }
226
+
227
+ json_data: Dict[str, str] = {
228
+ "decision": "continue",
229
+ "lang": self.app_language,
230
+ "sessionToken": session_token,
231
+ "appVersion": self.app_version_number,
232
+ }
233
+
234
+ response = requests.post(
235
+ "https://katalk.kakao.com/android/account2/confirm-device-change",
236
+ headers=headers_browser,
237
+ json=json_data,
238
+ )
239
+
240
+ response_json = json.loads(response.text)
241
+
242
+ self.cb.put(("msg_dynamic", (None,), None))
243
+ if response_json["status"] != 0:
244
+ return False, f"Failed at confirm device change: {response.text}"
245
+
246
+ return True, ""
247
+
248
+ def passcode_callback(self) -> Tuple[bool, str]:
249
+ msg = I("Getting Kakao authorization token by Android login: Passcode callback")
250
+ self.cb.put(("msg_dynamic", (msg,), None))
251
+
252
+ response = requests.get(
253
+ "https://katalk.kakao.com/android/account2/passcode/callback",
254
+ headers=self.headers,
255
+ )
256
+
257
+ response_json = json.loads(response.text)
258
+
259
+ self.cb.put(("msg_dynamic", (None,), None))
260
+ if response_json["status"] != 0:
261
+ return False, f"Failed at passcode callback: {response.text}"
262
+
263
+ return True, ""
264
+
265
+ def skip_restoration(self) -> Tuple[bool, str]:
266
+ msg = I("Getting Kakao authorization token by Android login: Skip restoration")
267
+ self.cb.put(("msg_dynamic", (msg,), None))
268
+
269
+ response = requests.post(
270
+ "https://katalk.kakao.com/android/account2/skip-restoration",
271
+ headers=self.headers,
272
+ )
273
+ response_json = json.loads(response.text)
274
+
275
+ if response_json["status"] != 0:
276
+ return False, f"Failed at skip restoration: {response.text}"
277
+
278
+ self.nickname = response_json.get("viewData", {}).get("nickname")
279
+
280
+ self.cb.put(("msg_dynamic", (None,), None))
281
+ if self.nickname is None:
282
+ return False, f"Failed at passcode callback: {response.text}"
283
+
284
+ return True, ""
285
+
286
+ def get_profile(self) -> Tuple[bool, str]:
287
+ msg = I("Getting Kakao authorization token by Android login: Get profile")
288
+ self.cb.put(("msg_dynamic", (msg,), None))
289
+
290
+ json_data = {
291
+ "nickname": self.nickname,
292
+ "profileImageFlag": 1,
293
+ "friendAutomation": True,
294
+ }
295
+
296
+ response = requests.post(
297
+ "https://katalk.kakao.com/android/account2/profile",
298
+ headers=self.headers,
299
+ json=json_data,
300
+ )
301
+
302
+ response_json = json.loads(response.text)
303
+
304
+ self.cb.put(("msg_dynamic", (None,), None))
305
+ if response_json["status"] != 0:
306
+ return False, f"Failed at get profile: {response.text}"
307
+
308
+ self.access_token = response_json["signupData"]["oauth2Token"]["accessToken"]
309
+
310
+ return True, ""
311
+
312
+ def get_cred(self) -> Tuple[Optional[str], str]:
313
+ auth_token = None
314
+
315
+ steps = (
316
+ self.login,
317
+ self.get_country_iso,
318
+ self.enter_phone,
319
+ self.confirm_device_change,
320
+ self.passcode_callback,
321
+ self.skip_restoration,
322
+ self.get_profile,
323
+ )
324
+
325
+ for step in steps:
326
+ success, msg = step()
327
+ if not success:
328
+ return None, msg
329
+
330
+ auth_token = self.access_token + "-" + self.device_uuid
331
+ return auth_token, self.OK_MSG.format(auth_token)